[
  {
    "path": ".dockerignore",
    "content": "# Ignore node_modules (local dependencies)\nnode_modules\n\n# Ignore build artifacts\ndist\n\n# Ignore configuration files that shouldn't be shared\n.env\n.vscode\n.idea\n\n# Ignore miscellaneous system files and logs\n.DS_Store\nnpm-debug.log\nyarn-debug.log\nyarn-error.log\npackage-lock.json\n\n# Ignore Git-specific files\n.git\n.gitignore\n\n/docs"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: 🐞 Bug Report\ndescription: Found something that doesn't work as expected?\nbody:\n  - type: dropdown\n    id: Environment\n    attributes:\n      label: Environment\n      description: How are you using Duck-UI?\n      options:\n        - NPM build\n        - Docker\n        - Other\n    validations:\n      required: true\n  - type: textarea\n    id: repro\n    attributes:\n      label: How did you encounter the bug?\n      description: How can this bug be reproduced? Please provide steps to reproduce.\n      placeholder: |-\n        1. Run Docker container... \n        2. npm run build...\n        3. Go to...\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: What did you expect?\n      description: What it supposed to happen? What did you expect to see?\n    validations:\n      required: true\n  - type: textarea\n    id: actual\n    attributes:\n      label: Actual Result\n      description: What was the accual result?\n    validations:\n      required: true\n  - type: dropdown\n    id: Browser\n    attributes:\n      label: Browser\n      description: What browser are you using?\n      options:\n        - Chrome\n        - Firefox\n        - Edge\n        - Safari\n        - Brave\n        - Other\n    validations:\n      required: true\n  # browser version with instructions to check it \n  - type: textarea\n    id: browser-version\n    attributes:\n      label: Browser Version\n      description: What version of the browser are you using?\n      placeholder: e.g. 125.0.0\n    validations:\n      required: true\n  - type: textarea\n    id: version\n    attributes:\n      label: Version\n      description: What version of Duck-UI are you using?\n      placeholder: e.g. 1.5.0\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: |-\n        ### All done, now, just submit the issue and I will do my best to take care of it!  🙏\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: 💡 Feature Request\ndescription: Tell us about something Duck-UI doesn't do yet, but should!\nbody:\n  - type: textarea\n    id: idea\n    attributes:\n      label: Idea Statement\n      description: Which is the feature you would like to see implemented?\n      placeholder: |-\n        I want to be able to do anything I want, whenever I want. Because my ideas are the best.\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: Feature implementation brainstorm\n      description: All your ideas are welcome, let's brainstorm together.\n      placeholder: |-\n        Create the next big feature that will all our problems.\n    validations:\n      required: false\n  - type: markdown\n    attributes:\n      value: |-\n        ## Thanks 🙏\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/workflows/2-docker-build.yml",
    "content": "# .github/workflows/2-docker-build.yml\nname: Build and Push Docker Image\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - '**.md'\n      - 'docs/**'\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - '**.md'\n      - 'docs/**'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: |\n      (!contains(github.event.head_commit.message, 'docker-false') && !contains(github.event.head_commit.message, 'docs-only')) ||\n      contains(github.event.head_commit.message, 'docker-only')\n    permissions:\n      contents: read\n      packages: write\n    \n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Log in to GitHub Container Registry\n        if: github.event_name != 'pull_request'\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GHCR_PAT }}\n\n      - name: Extract metadata (tags, labels)\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ghcr.io/${{ github.repository }}\n          tags: |\n            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}\n            type=sha,format=long\n            type=ref,event=pr\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v5\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          cache-from: type=gha\n          cache-to: type=gha,mode=max"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [main]\n    paths-ignore:\n      - \"**.md\"\n      - \"docs/**\"\n  pull_request:\n    branches: [main]\n    paths-ignore:\n      - \"**.md\"\n      - \"docs/**\"\n\njobs:\n  lint:\n    name: Lint & Format\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - run: bun install --frozen-lockfile\n      - run: bun run lint\n      - run: bun run format:check\n\n  typecheck:\n    name: Type Check\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - run: bun install --frozen-lockfile\n      - run: bun run typecheck\n\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - run: bun install --frozen-lockfile\n      - run: bun run test\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    needs: [lint, typecheck, test]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - run: bun install --frozen-lockfile\n      - run: bun run build\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: dist\n          path: dist/\n          retention-days: 7\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\n\n# Editor directories and files\n.vscode/*\n/vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n.env\n\npost.md\nCLAUDE.md"
  },
  {
    "path": ".husky/pre-commit",
    "content": "bunx lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "content": "dist\nnode_modules\nbun.lockb\n*.wasm\npublic\ndocs/.vitepress/cache\ndocs/.vitepress/dist\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"printWidth\": 100,\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"always\",\n  \"endOfLine\": \"lf\"\n}\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Use an official Node runtime as a parent image with bun\nFROM oven/bun:1-alpine AS build\n\nARG DUCK_UI_BASEPATH=\"/\"\n\n# Set the working directory\nWORKDIR /app\n\n# Copy package.json and bun.lockb (if exists)\nCOPY package.json bun.lockb* ./\n\n# Install dependencies\nRUN bun install --frozen-lockfile\n\n# Bundle app source inside Docker image\nCOPY . .\n\n# Build the app\nRUN bun run build\n\n# Use a second stage to reduce image size\nFROM oven/bun:1-alpine\n\n# Set the working directory for the second stage\nWORKDIR /app\n\n# Copy the build directory from the first stage to the second stage\nCOPY --from=build /app/dist /app\n\n# Copy the injection script and serve config (COOP/COEP headers for OPFS)\nCOPY inject-env.js /app/\nCOPY serve.json /app/\n\n# Install just serve for serving the built app\nRUN bun add serve\n\n# Expose port 5522\nEXPOSE 5522\n\n# Define environment variables\nENV DUCK_UI_EXTERNAL_CONNECTION_NAME=\"\"\nENV DUCK_UI_EXTERNAL_HOST=\"\"\nENV DUCK_UI_EXTERNAL_PORT=\"\"\nENV DUCK_UI_EXTERNAL_USER=\"\"\nENV DUCK_UI_EXTERNAL_PASS=\"\"\nENV DUCK_UI_EXTERNAL_DATABASE_NAME=\"\"\n\n# Create user and change ownership\nRUN addgroup -S duck-group -g 1001 && adduser -S duck-user -u 1001 -G duck-group\nRUN chown -R duck-user:duck-group /app\n\nUSER duck-user\n\n# Run the injection script then serve using bunx\nCMD bun inject-env.js && bunx serve -s -l 5522 -c serve.json"
  },
  {
    "path": "LICENSE.md",
    "content": "# License\n\n## DUCK UI - Apache License 2.0\n\nCopyright 2025 Caio Ricciuti \n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n## What does this mean?\n\nThe Apache License 2.0 is a permissive open source license that provides users with extensive freedom to use, modify, and distribute the software, while also offering robust legal protection through its patent grant and clear contribution terms.\n\n### You are free to:\n\n- ✅ Use the software commercially\n- ✅ Modify the software\n- ✅ Distribute the software\n- ✅ Use the software for private use\n- ✅ Sublicense the software\n- ✅ Use patents claims of contributors to the code\n\n### Under the following conditions:\n\n- ℹ️ Include the original copyright notice\n- ℹ️ Include a copy of the license\n- ℹ️ State significant changes made to the software\n- ℹ️ Include the NOTICE file (if present) with attribution notes\n\n### With the understanding that:\n\n- ⚠️ The software is provided \"as is\", without warranty of any kind\n- ⚠️ The authors cannot be held liable for damages\n- ⚠️ Trademark use is not granted except as required for describing the origin of the work\n\n## Key Benefits of Apache 2.0\n\n### Patent Protection\nUnlike simpler licenses like MIT, Apache 2.0 includes an express patent grant from contributors to users. This means that if a contributor has patents that cover their contribution, they automatically grant you a license to use those patents.\n\n### Clear Contribution Terms\nThe license explicitly states that any contributions are assumed to be under the same Apache 2.0 license unless otherwise specified, providing clarity for collaborative development.\n\n### Compatibility\nApache 2.0 is compatible with many other open source licenses and is widely accepted in both open source and commercial contexts.\n\n## Third-Party Licenses\n\nCH-UI is built on top of several open-source projects. We'd like to acknowledge and give credit to these projects:\n\n- [ClickHouse](https://github.com/ClickHouse/ClickHouse) - Apache 2.0 License\n- [React](https://github.com/facebook/react) - MIT License\n- [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) - MIT License\n- [Zustand](https://github.com/pmndrs/zustand) - MIT License\n- [Monaco Editor](https://github.com/microsoft/monaco-editor) - MIT License\n- [Lucide Icons](https://github.com/lucide-icons/lucide) - ISC License\n\nFor the full text of these licenses, please visit the respective project repositories."
  },
  {
    "path": "README.md",
    "content": "# <img src=\"./public/logo.png\" alt=\"Duck-UI Logo\" title=\"Duck-UI Logo\" width=\"50\"> Duck-UI\n\nDuck-UI is a web-based interface for interacting with DuckDB, a high-performance analytical database system. This project leverages DuckDB's WebAssembly (WASM) capabilities to provide a seamless and efficient user experience directly in the browser.\n\n# [Official Docs](https://duckui.com?utm_source=github&utm_medium=readme) 🚀\n#  [Demo](https://demo.duckui.com?utm_source=github&utm_medium=readme) 💻\n\n\n## Features\n\n- **SQL Editor**: Write and execute SQL queries with syntax highlighting and auto-completion.\n- **Data Import**: Import data from CSV, JSON, Parquet, and Arrow files.\n- **Data Explorer**: Browse and manage databases and tables.\n- **Query History**: View and manage your recent SQL queries.\n\n## Getting Started\n\n\n### Docker (Recommended)\n\n```bash\ndocker run -p 5522:5522 ghcr.io/caioricciuti/duck-ui:latest\n```\n\nOpen your browser and navigate to `http://localhost:5522`.\n\n### Environment Variables\n\nYou can customize Duck-UI behavior using environment variables:\n\n```bash\n# For external DuckDB connections\ndocker run -p 5522:5522 \\\n  -e DUCK_UI_EXTERNAL_CONNECTION_NAME=\"My DuckDB Server\" \\\n  -e DUCK_UI_EXTERNAL_HOST=\"http://duckdb-server\" \\\n  -e DUCK_UI_EXTERNAL_PORT=\"8000\" \\\n  -e DUCK_UI_EXTERNAL_USER=\"username\" \\\n  -e DUCK_UI_EXTERNAL_PASS=\"password\" \\\n  -e DUCK_UI_EXTERNAL_DATABASE_NAME=\"my_database\" \\\n  -e DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS=\"true\" \\\n  ghcr.io/caioricciuti/duck-ui:latest\n```\n\n| Runtime Variable | Description | Default |\n|----------|-------------|---------|\n| `DUCK_UI_EXTERNAL_CONNECTION_NAME` | Name for the external connection | \"\" |\n| `DUCK_UI_EXTERNAL_HOST` | Host URL for external DuckDB | \"\" |\n| `DUCK_UI_EXTERNAL_PORT` | Port for external DuckDB | null |\n| `DUCK_UI_EXTERNAL_USER` | Username for external connection | \"\" |\n| `DUCK_UI_EXTERNAL_PASS` | Password for external connection | \"\" |\n| `DUCK_UI_EXTERNAL_DATABASE_NAME` | Database name for external connection | \"\" |\n| `DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS` | Allow unsigned extensions in DuckDB | false |\n| `DUCK_UI_DUCKDB_WASM_USE_CDN` | Load DuckDB WASM from CDN (ignored when build-time `DUCK_UI_DUCKDB_WASM_CDN_ONLY=true`) | false |\n| `DUCK_UI_DUCKDB_WASM_BASE_URL` | Custom CDN base URL (used when `DUCK_UI_DUCKDB_WASM_USE_CDN=true`) | auto jsDelivr |\n\n| Build-time Variable | Description | Default |\n|----------|-------------|---------|\n| `DUCK_UI_DUCKDB_WASM_CDN_ONLY` | Build a CDN-only artifact (local DuckDB WASM assets are not bundled). | false |\n\nWhen `DUCK_UI_DUCKDB_WASM_CDN_ONLY=true`, runtime `DUCK_UI_DUCKDB_WASM_USE_CDN=false` cannot switch back to local WASM.\n\n\n\n### Prerequisites\n\n- Node.js >= 20.x\n- npm >= 10.x\n\n### Installation\n\n1. Clone the repository:\n\n   ```bash\n   git clone https://github.com/caioricciuti/duck-ui.git\n   cd duck-ui\n   ```\n\n2. Install dependencies:\n\n   ```bash\n   npm install\n   # or\n   yarn install\n   ```\n\n### Running the Application\n\n1. Start the development server:\n\n   ```bash\n   npm run dev\n   # or\n   yarn dev\n   ```\n\n2. Open your browser and navigate to `http://localhost:5173`.\n\n### Building for Production\n\nTo create a production build, run:\n\n```bash\nnpm run build\n# or\nyarn build\n```\n\nThe output will be in the `dist` directory.\n\n### Running with Docker\n\n1. Build the Docker image:\n\n   ```bash\n   docker build -t duck-ui .\n   ```\n\n2. Run the Docker container:\n\n   ```bash\n   docker run -p 5522:5522 duck-ui\n   ```\n\n3. Open your browser and navigate to `http://localhost:5522`.\n\n## Usage\n\n### SQL Editor\n\n- Write your SQL queries in the editor.\n- Use `Cmd/Ctrl + Enter` to execute the query.\n- View the results in the results pane.\n\n### Data Import\n\n- Click on the \"Import Files\" button to upload CSV, JSON, Parquet, or Arrow files.\n- Configure the table name and import settings.\n- For CSV files, you can customize import options:\n  - Header row detection\n  - Auto-detection of column types\n  - Delimiter specification\n  - Error handling (ignore errors, null padding for missing columns)\n- View the imported data in the Data Explorer.\n\n### Data Explorer\n\n- Browse through the databases and tables.\n- Preview table data and view table schemas.\n- Delete tables if needed.\n\n### Query History\n\n- Access your recent queries from the Query History section.\n- Copy queries to the clipboard or re-execute them.\n\n### Theme Toggle\n\n- Switch between light and dark themes using the theme toggle button.\n\n### Keyboard Shortcuts\n\n- `Cmd/Ctrl + B`: Expand/Shrink Sidebar\n- `Cmd/Ctrl + K`: Open Search Bar\n- `Cmd/Ctrl + Enter`: Run Query\n- `Cmd/Ctrl + Shift + Enter`: Run highlighted query\n\n## Contributing\n\nContributions are welcome! Please follow these steps to contribute:\n\n1. Fork the repository.\n2. Create a new branch (`git checkout -b feature/your-feature`).\n3. Commit your changes (`git commit -m 'Add some feature'`).\n4. Push to the branch (`git push origin feature/your-feature`).\n5. Open a pull request.\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details.\n\n## Acknowledgements\n\n- [DuckDB](https://duckdb.org/)\n- [React](https://reactjs.org/)\n- [Tailwind CSS](https://tailwindcss.com/)\n- [Zustand](https://github.com/pmndrs/zustand)\n- [Lucide Icons](https://lucide.dev/)\n\n## Contact\n\nFor any inquiries or support, please contact [Caio Ricciuti](https://github.com/caioricciuti).\n\n## Sponsors\n\nThis project is sponsored by:\n\n### [Ibero Data](https://iberodata.es/) \n<img src=\"https://iberodata.es/logo.png\" alt=\"Ibero Data Logo\" title=\"Ibero Data Logo\" width=\"100\">\n\n### [qxip](https://qxip.net/?utm_source=duck-ui&utm_medium=sponsorship) \n\n<img src=\"https://qxip.net/images/qxip.png\" alt=\"qxip\" title=\"qxip Logo\" width=\"150\">\n\n\n\n<br/>\n\nWant to be a sponsor? [Contact us](mailto:caio.ricciuti+sponsorship@outlook.com).\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.js\",\n    \"css\": \"src/index.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"iconLibrary\": \"lucide\"\n}"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  duck-ui:\n    image: ghcr.io/caioricciuti/duck-ui:latest\n    restart: always\n    ports:\n      - \"${DUCK_UI_PORT:-5522}:5522\"\n    environment:\n      # External connection (optional)\n      - DUCK_UI_EXTERNAL_CONNECTION_NAME=${DUCK_UI_EXTERNAL_CONNECTION_NAME:-}\n      - DUCK_UI_EXTERNAL_HOST=${DUCK_UI_EXTERNAL_HOST:-}\n      - DUCK_UI_EXTERNAL_PORT=${DUCK_UI_EXTERNAL_PORT:-}\n      - DUCK_UI_EXTERNAL_USER=${DUCK_UI_EXTERNAL_USER:-}\n      - DUCK_UI_EXTERNAL_PASS=${DUCK_UI_EXTERNAL_PASS:-}\n      - DUCK_UI_EXTERNAL_DATABASE_NAME=${DUCK_UI_EXTERNAL_DATABASE_NAME:-}\n      # DuckDB configuration\n      - DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS=${DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS:-false}"
  },
  {
    "path": "eslint.config.js",
    "content": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reactRefresh from 'eslint-plugin-react-refresh'\nimport tseslint from 'typescript-eslint'\n\nexport default tseslint.config(\n  { ignores: ['dist', 'docs'] },\n  {\n    extends: [js.configs.recommended, ...tseslint.configs.recommended],\n    files: ['**/*.{ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 2020,\n      globals: globals.browser,\n    },\n    plugins: {\n      'react-hooks': reactHooks,\n      'react-refresh': reactRefresh,\n    },\n    rules: {\n      ...reactHooks.configs.recommended.rules,\n      'react-refresh/only-export-components': [\n        'warn',\n        { allowConstantExport: true },\n      ],\n      '@typescript-eslint/no-unused-vars': [\n        'error',\n        { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },\n      ],\n    },\n  },\n)\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/png\" href=\"/logo-padding.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Duck UI</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "inject-env.js",
    "content": "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nconst indexHtmlPath = path.join(__dirname, \"index.html\");\nlet indexHtmlContent = fs.readFileSync(indexHtmlPath, \"utf8\");\n\n// Inject the environment variables\nconst envVars = {\n  DUCK_UI_EXTERNAL_CONNECTION_NAME:\n    process.env.DUCK_UI_EXTERNAL_CONNECTION_NAME || \"\",\n  DUCK_UI_EXTERNAL_HOST: process.env.DUCK_UI_EXTERNAL_HOST || \"\",\n  DUCK_UI_EXTERNAL_PORT: process.env.DUCK_UI_EXTERNAL_PORT || null,\n  DUCK_UI_EXTERNAL_USER: process.env.DUCK_UI_EXTERNAL_USER || \"\",\n  DUCK_UI_EXTERNAL_PASS: process.env.DUCK_UI_EXTERNAL_PASS || \"\",\n  DUCK_UI_EXTERNAL_DATABASE_NAME:\n    process.env.DUCK_UI_EXTERNAL_DATABASE_NAME || \"\",\n  // Add new configuration for DuckDB settings\n  DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS:\n    process.env.DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS === \"true\" || false,\n  DUCK_UI_DUCKDB_WASM_USE_CDN:\n    process.env.DUCK_UI_DUCKDB_WASM_USE_CDN === \"true\" || false,\n  DUCK_UI_DUCKDB_WASM_BASE_URL:\n    process.env.DUCK_UI_DUCKDB_WASM_BASE_URL || \"\"\n};\n\nconst scriptContent = `\n<script>\n  window.env = ${JSON.stringify(envVars)};\n</script>\n`;\n\n// Insert the script just before the closing </head> tag\nindexHtmlContent = indexHtmlContent.replace(\n  \"</head>\",\n  `${scriptContent}</head>`\n);\n\nfs.writeFileSync(indexHtmlPath, indexHtmlContent);\n\nconsole.log(\"Environment variables injected successfully\");\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"duck-ui\",\n  \"private\": true,\n  \"version\": \"0.0.39\",\n  \"release_date\": \"2026-04-13\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"lint\": \"eslint .\",\n    \"format\": \"prettier --write \\\"src/**/*.{ts,tsx,css}\\\"\",\n    \"format:check\": \"prettier --check \\\"src/**/*.{ts,tsx,css}\\\"\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"preview\": \"vite preview\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"prepare\": \"husky\"\n  },\n  \"dependencies\": {\n    \"@dnd-kit/core\": \"^6.3.1\",\n    \"@dnd-kit/sortable\": \"^10.0.0\",\n    \"@duckdb/duckdb-wasm\": \"1.33.1-dev45.0\",\n    \"@hookform/resolvers\": \"^5.2.2\",\n    \"@mlc-ai/web-llm\": \"^0.2.82\",\n    \"@radix-ui/react-accordion\": \"^1.2.12\",\n    \"@radix-ui/react-alert-dialog\": \"^1.1.15\",\n    \"@radix-ui/react-aspect-ratio\": \"^1.1.8\",\n    \"@radix-ui/react-checkbox\": \"^1.3.3\",\n    \"@radix-ui/react-collapsible\": \"^1.1.12\",\n    \"@radix-ui/react-context-menu\": \"^2.2.16\",\n    \"@radix-ui/react-dialog\": \"^1.1.15\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.16\",\n    \"@radix-ui/react-hover-card\": \"^1.1.15\",\n    \"@radix-ui/react-label\": \"^2.1.8\",\n    \"@radix-ui/react-menubar\": \"^1.1.16\",\n    \"@radix-ui/react-navigation-menu\": \"^1.2.14\",\n    \"@radix-ui/react-popover\": \"^1.1.15\",\n    \"@radix-ui/react-progress\": \"^1.1.8\",\n    \"@radix-ui/react-radio-group\": \"^1.3.8\",\n    \"@radix-ui/react-scroll-area\": \"^1.2.10\",\n    \"@radix-ui/react-select\": \"^2.2.6\",\n    \"@radix-ui/react-separator\": \"^1.1.8\",\n    \"@radix-ui/react-slot\": \"^1.2.4\",\n    \"@radix-ui/react-switch\": \"^1.2.6\",\n    \"@radix-ui/react-tabs\": \"^1.1.13\",\n    \"@radix-ui/react-tooltip\": \"^1.2.8\",\n    \"@tailwindcss/typography\": \"^0.5.19\",\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@tanstack/react-table\": \"^8.21.3\",\n    \"@tanstack/react-virtual\": \"^3.13.23\",\n    \"@types/dompurify\": \"^3.2.0\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"cmdk\": \"^1.1.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"dompurify\": \"^3.3.3\",\n    \"exceljs\": \"^4.4.0\",\n    \"framer-motion\": \"^12.38.0\",\n    \"html2canvas\": \"^1.4.1\",\n    \"lucide-react\": \"^0.546.0\",\n    \"marked\": \"^17.0.6\",\n    \"monaco-editor\": \"^0.55.1\",\n    \"openai\": \"^6.34.0\",\n    \"papaparse\": \"^5.5.3\",\n    \"react\": \"^19.2.5\",\n    \"react-dom\": \"^19.2.5\",\n    \"react-error-boundary\": \"^6.1.1\",\n    \"react-hook-form\": \"^7.72.1\",\n    \"react-markdown\": \"^10.1.0\",\n    \"react-resizable-panels\": \"^3.0.6\",\n    \"react-router\": \"^7.14.0\",\n    \"sonner\": \"^2.0.7\",\n    \"sql-formatter\": \"^15.7.3\",\n    \"tailwind-merge\": \"^3.5.0\",\n    \"uplot\": \"^1.6.32\",\n    \"vaul\": \"^1.1.2\",\n    \"zod\": \"^4.3.6\",\n    \"zustand\": \"^5.0.12\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.39.4\",\n    \"@types/node\": \"^24.12.2\",\n    \"@types/papaparse\": \"^5.5.2\",\n    \"@types/react\": \"^19.2.14\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@vitejs/plugin-react\": \"^5.2.0\",\n    \"baseline-browser-mapping\": \"^2.10.18\",\n    \"esbuild\": \"^0.25.12\",\n    \"eslint\": \"^9.39.4\",\n    \"eslint-plugin-react-hooks\": \"^7.0.1\",\n    \"eslint-plugin-react-refresh\": \"^0.4.26\",\n    \"globals\": \"^16.5.0\",\n    \"husky\": \"^9.1.7\",\n    \"lint-staged\": \"^16.4.0\",\n    \"prettier\": \"^3.8.2\",\n    \"tailwindcss\": \"^4.2.2\",\n    \"typescript\": \"~5.9.3\",\n    \"typescript-eslint\": \"^8.58.2\",\n    \"vite\": \"^7.3.2\",\n    \"vitest\": \"^4.1.4\"\n  },\n  \"description\": \"Duck-UI is a web-based interface for interacting with DuckDB, a high-performance analytical database system. This project leverages DuckDB's WebAssembly (WASM) capabilities to provide a seamless and efficient user experience directly in the browser.\",\n  \"main\": \"eslint.config.js\",\n  \"directories\": {\n    \"doc\": \"docs\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/caioricciuti/duck-ui.git\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/caioricciuti/duck-ui/issues\"\n  },\n  \"homepage\": \"https://github.com/caioricciuti/duck-ui#readme\",\n  \"lint-staged\": {\n    \"src/**/*.{ts,tsx,css}\": \"prettier --write\"\n  },\n  \"overrides\": {\n    \"brace-expansion\": \"^2.0.3\",\n    \"picomatch\": \"^4.0.4\",\n    \"flatted\": \"^3.4.2\"\n  }\n}\n"
  },
  {
    "path": "public/databases/README.md",
    "content": "# Embedded Databases\n\nThis directory allows you to embed DuckDB database files (`.db`) that will be automatically loaded when DuckUI starts. This is perfect for:\n\n- Deploying DuckUI with demo data\n- Distributing pre-configured databases\n- Creating self-contained analytical dashboards\n\n## How to Add Your Database\n\n1. **Place your `.db` file in this directory**\n   ```\n   public/databases/my-database.db\n   ```\n\n2. **Register it in `manifest.json`**\n   ```json\n   {\n     \"databases\": [\n       {\n         \"name\": \"My Database\",\n         \"file\": \"my-database.db\",\n         \"description\": \"Description of your database\",\n         \"autoLoad\": true\n       }\n     ]\n   }\n   ```\n\n3. **Build and deploy**\n   ```bash\n   bun run build\n   ```\n\n## Manifest Format\n\nEach database entry in `manifest.json` supports:\n\n- **`name`** (required): Display name in the UI\n- **`file`** (required): Filename of the `.db` file in this directory\n- **`description`** (optional): Description shown in the UI\n- **`autoLoad`** (optional, default: `true`): Whether to load on startup\n\n## Example\n\n```json\n{\n  \"databases\": [\n    {\n      \"name\": \"Sales Demo\",\n      \"file\": \"sales-demo.db\",\n      \"description\": \"Sample sales data from 2023\",\n      \"autoLoad\": true\n    },\n    {\n      \"name\": \"Analytics\",\n      \"file\": \"analytics.db\",\n      \"description\": \"Web analytics data\",\n      \"autoLoad\": false\n    }\n  ]\n}\n```\n\n## Notes\n\n- Database files are fetched and loaded in the browser\n- Large database files will increase initial load time\n- All databases are loaded into memory (consider file sizes)\n- You can attach/detach databases dynamically from the SQL editor\n"
  },
  {
    "path": "public/databases/manifest.json",
    "content": "{\n  \"databases\": []\n}\n"
  },
  {
    "path": "serve.json",
    "content": "{\n  \"headers\": [\n    {\n      \"source\": \"**/*\",\n      \"headers\": [\n        { \"key\": \"Cross-Origin-Opener-Policy\", \"value\": \"same-origin\" },\n        { \"key\": \"Cross-Origin-Embedder-Policy\", \"value\": \"credentialless\" }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/components/charts/ChartVisualizationPro.tsx",
    "content": "/**\n * Professional Chart Visualization Component\n * Features: Auto-chart, live preview, multi-series, customization, export\n * Powered by uPlot (canvas-based, lightweight)\n */\n\nimport React, { useState, useRef, useMemo, useEffect, useCallback } from \"react\";\nimport uPlot from \"uplot\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { MultiSelect } from \"@/components/ui/multi-select\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Download,\n  BarChart3,\n  LineChart,\n  PieChart as PieChartIcon,\n  ScatterChart,\n  AreaChart,\n  Settings2,\n  RotateCcw,\n  Layers,\n  TrendingUp,\n  CircleDot,\n  ArrowUpDown,\n} from \"lucide-react\";\nimport { toast } from \"sonner\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport { formatNumber, formatNumberWithSuffix, shortenLabel } from \"@/lib/chartUtils\";\nimport { transformData, isNumericColumn, suggestChartTypes } from \"@/lib/chartDataTransform\";\nimport { exportChartAsPNG } from \"@/lib/chartExport\";\nimport UPlotChart from \"./UPlotChart\";\nimport { tooltipPlugin } from \"./tooltipPlugin\";\nimport type { QueryResult, ChartConfig, ChartType, DataTransform } from \"@/store\";\n\ninterface ChartVisualizationProProps {\n  result: QueryResult;\n  chartConfig?: ChartConfig;\n  onConfigChange: (config: ChartConfig | undefined) => void;\n}\n\n// Enhanced color palette\nconst DEFAULT_COLORS = [\n  \"#D99B43\",\n  \"#8B5CF6\",\n  \"#3B82F6\",\n  \"#10B981\",\n  \"#F59E0B\",\n  \"#EF4444\",\n  \"#EC4899\",\n  \"#6366F1\",\n  \"#14B8A6\",\n  \"#F97316\",\n];\n\n// Chart type display labels with icons\nconst CHART_TYPE_INFO: Record<string, { label: string; icon: React.ElementType }> = {\n  bar: { label: \"Bar\", icon: BarChart3 },\n  grouped_bar: { label: \"Grouped Bar\", icon: Layers },\n  stacked_bar: { label: \"Stacked Bar\", icon: Layers },\n  line: { label: \"Line\", icon: LineChart },\n  area: { label: \"Area\", icon: AreaChart },\n  stacked_area: { label: \"Stacked Area\", icon: TrendingUp },\n  pie: { label: \"Pie\", icon: PieChartIcon },\n  donut: { label: \"Donut\", icon: CircleDot },\n  scatter: { label: \"Scatter\", icon: ScatterChart },\n};\n\n// ── SVG Pie/Donut Chart ──────────────────────────────────────────────────────\n\nfunction PieChartDisplay({\n  data,\n  xKey,\n  yKey,\n  colors,\n  isDonut,\n  innerRadius = 0.45,\n  theme,\n}: {\n  data: Record<string, unknown>[];\n  xKey: string;\n  yKey: string;\n  colors: string[];\n  isDonut: boolean;\n  innerRadius?: number;\n  theme: string;\n}) {\n  const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);\n  const total = data.reduce((sum, row) => sum + (Number(row[yKey]) || 0), 0);\n\n  if (total === 0)\n    return (\n      <div className=\"flex items-center justify-center h-full text-muted-foreground\">No data</div>\n    );\n\n  const slices: { label: string; value: number; pct: number; color: string }[] = [];\n  let cumAngle = -Math.PI / 2;\n  const arcs: {\n    d: string;\n    color: string;\n    midAngle: number;\n    pct: number;\n    label: string;\n    value: number;\n  }[] = [];\n\n  data.forEach((row, i) => {\n    const value = Number(row[yKey]) || 0;\n    const pct = value / total;\n    const angle = pct * 2 * Math.PI;\n    const startAngle = cumAngle;\n    const endAngle = cumAngle + angle;\n    const midAngle = startAngle + angle / 2;\n    const color = colors[i % colors.length];\n\n    slices.push({ label: String(row[xKey]), value, pct, color });\n\n    const outerR = 1;\n    const innerR = isDonut ? innerRadius : 0;\n    const largeArc = angle > Math.PI ? 1 : 0;\n\n    const x1 = Math.cos(startAngle) * outerR;\n    const y1 = Math.sin(startAngle) * outerR;\n    const x2 = Math.cos(endAngle) * outerR;\n    const y2 = Math.sin(endAngle) * outerR;\n    const ix1 = Math.cos(endAngle) * innerR;\n    const iy1 = Math.sin(endAngle) * innerR;\n    const ix2 = Math.cos(startAngle) * innerR;\n    const iy2 = Math.sin(startAngle) * innerR;\n\n    const d = isDonut\n      ? `M ${x1} ${y1} A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2} L ${ix1} ${iy1} A ${innerR} ${innerR} 0 ${largeArc} 0 ${ix2} ${iy2} Z`\n      : `M 0 0 L ${x1} ${y1} A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2} Z`;\n\n    arcs.push({ d, color, midAngle, pct, label: String(row[xKey]), value });\n    cumAngle = endAngle;\n  });\n\n  const strokeColor = theme === \"dark\" ? \"#1a1a1a\" : \"#fff\";\n\n  return (\n    <div className=\"flex items-center justify-center h-full gap-6\">\n      {/* Chart */}\n      <div className=\"relative flex-shrink-0\">\n        <svg\n          viewBox=\"-1.3 -1.3 2.6 2.6\"\n          className=\"w-full max-w-[360px] max-h-[360px]\"\n          style={{ minWidth: 200 }}\n        >\n          {arcs.map((arc, i) => {\n            const isHovered = hoveredIdx === i;\n            const tx = isHovered ? Math.cos(arc.midAngle) * 0.06 : 0;\n            const ty = isHovered ? Math.sin(arc.midAngle) * 0.06 : 0;\n            return (\n              <g key={i}>\n                <path\n                  d={arc.d}\n                  fill={arc.color}\n                  stroke={strokeColor}\n                  strokeWidth={0.02}\n                  opacity={hoveredIdx !== null && !isHovered ? 0.4 : 1}\n                  transform={`translate(${tx}, ${ty})`}\n                  style={{ transition: \"opacity 0.2s, transform 0.2s\" }}\n                  onMouseEnter={() => setHoveredIdx(i)}\n                  onMouseLeave={() => setHoveredIdx(null)}\n                />\n                {arc.pct >= 0.05 && (\n                  <text\n                    x={Math.cos(arc.midAngle) * (isDonut ? 0.72 : 0.6) + tx}\n                    y={Math.sin(arc.midAngle) * (isDonut ? 0.72 : 0.6) + ty}\n                    textAnchor=\"middle\"\n                    dominantBaseline=\"central\"\n                    fill=\"white\"\n                    fontSize=\"0.11\"\n                    fontWeight=\"600\"\n                    pointerEvents=\"none\"\n                    style={{ transition: \"all 0.2s\" }}\n                  >\n                    {`${(arc.pct * 100).toFixed(0)}%`}\n                  </text>\n                )}\n              </g>\n            );\n          })}\n          {/* Donut center total */}\n          {isDonut && (\n            <g>\n              <text\n                x=\"0\"\n                y=\"-0.06\"\n                textAnchor=\"middle\"\n                dominantBaseline=\"central\"\n                fill={theme === \"dark\" ? \"#999\" : \"#666\"}\n                fontSize=\"0.12\"\n              >\n                Total\n              </text>\n              <text\n                x=\"0\"\n                y=\"0.12\"\n                textAnchor=\"middle\"\n                dominantBaseline=\"central\"\n                fill={theme === \"dark\" ? \"#e5e5e5\" : \"#1a1a1a\"}\n                fontSize=\"0.18\"\n                fontWeight=\"700\"\n              >\n                {formatNumberWithSuffix(total)}\n              </text>\n            </g>\n          )}\n        </svg>\n        {/* Hover tooltip */}\n        {hoveredIdx !== null && (\n          <div className=\"absolute top-2 left-1/2 -translate-x-1/2 bg-popover border rounded-lg shadow-lg px-3 py-2 text-xs pointer-events-none z-10\">\n            <div className=\"font-medium\">{slices[hoveredIdx].label}</div>\n            <div className=\"text-muted-foreground\">\n              {formatNumber(slices[hoveredIdx].value)} ({(slices[hoveredIdx].pct * 100).toFixed(1)}\n              %)\n            </div>\n          </div>\n        )}\n      </div>\n      {/* Legend */}\n      <div className=\"flex flex-col gap-1.5 text-xs max-h-[320px] overflow-y-auto pr-2\">\n        {slices.map((s, i) => (\n          <div\n            key={i}\n            className=\"flex items-center gap-2 cursor-default\"\n            onMouseEnter={() => setHoveredIdx(i)}\n            onMouseLeave={() => setHoveredIdx(null)}\n          >\n            <span\n              className=\"w-2.5 h-2.5 rounded-full flex-shrink-0\"\n              style={{ backgroundColor: s.color }}\n            />\n            <span className=\"text-muted-foreground truncate max-w-[140px]\" title={s.label}>\n              {s.label}\n            </span>\n            <span className=\"font-medium ml-auto tabular-nums pl-2\">\n              {formatNumberWithSuffix(s.value)}\n            </span>\n            <span className=\"text-muted-foreground tabular-nums w-[3.5em] text-right\">\n              {(s.pct * 100).toFixed(1)}%\n            </span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n\n// ── XY Chart Legend ──────────────────────────────────────────────────────────\n\nfunction ChartLegend({\n  series,\n  colors,\n  onToggle,\n}: {\n  series: { label: string; show: boolean }[];\n  colors: string[];\n  onToggle: (idx: number) => void;\n}) {\n  if (series.length <= 1) return null;\n  return (\n    <div className=\"flex flex-wrap gap-x-4 gap-y-1 justify-center py-1 text-xs\">\n      {series.map((s, i) => (\n        <button\n          key={i}\n          className=\"flex items-center gap-1.5 cursor-pointer hover:opacity-80 transition-opacity\"\n          style={{ opacity: s.show ? 1 : 0.35 }}\n          onClick={() => onToggle(i)}\n          type=\"button\"\n        >\n          <span\n            className=\"w-2.5 h-2.5 rounded-full flex-shrink-0\"\n            style={{ backgroundColor: colors[i % colors.length] }}\n          />\n          <span className=\"text-muted-foreground\">{s.label}</span>\n        </button>\n      ))}\n    </div>\n  );\n}\n\n// ── Main Component ───────────────────────────────────────────────────────────\n\nexport const ChartVisualizationPro: React.FC<ChartVisualizationProProps> = ({\n  result,\n  chartConfig,\n  onConfigChange,\n}) => {\n  const { theme } = useTheme();\n  const chartRef = useRef<HTMLDivElement>(null);\n  const uPlotRef = useRef<uPlot | null>(null);\n  const [hiddenSeries, setHiddenSeries] = useState<Set<number>>(new Set());\n\n  // Get numeric columns for Y-axis\n  const numericColumns = useMemo(\n    () => result.columns.filter((col) => isNumericColumn(result.data, col)),\n    [result]\n  );\n\n  // Auto-chart: detect best config when no config exists\n  const autoDetect = useCallback((): ChartConfig => {\n    const xAxis =\n      result.columns.find((col) => !isNumericColumn(result.data, col)) || result.columns[0] || \"\";\n    const yCol = numericColumns[0] || result.columns[1] || \"\";\n    const suggested = suggestChartTypes(result, xAxis, yCol);\n    const type = (suggested[0] || \"bar\") as ChartType;\n    return {\n      type,\n      xAxis,\n      yAxis: yCol,\n      colors: DEFAULT_COLORS,\n      showGrid: true,\n      showValues: false,\n      smooth: false,\n      legend: { show: true, position: \"bottom\" },\n    };\n  }, [result, numericColumns]);\n\n  // On mount: auto-generate config if none provided\n  useEffect(() => {\n    if (!chartConfig && result.data.length > 0) {\n      onConfigChange(autoDetect());\n    }\n  }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n  // Live config update helper\n  const updateConfig = useCallback(\n    (updates: Partial<ChartConfig>) => {\n      const base = chartConfig || autoDetect();\n      onConfigChange({ ...base, ...updates });\n    },\n    [chartConfig, autoDetect, onConfigChange]\n  );\n\n  const updateTransform = useCallback(\n    (updates: Partial<DataTransform>) => {\n      const base = chartConfig || autoDetect();\n      onConfigChange({ ...base, transform: { ...base.transform, ...updates } });\n    },\n    [chartConfig, autoDetect, onConfigChange]\n  );\n\n  // Current effective config\n  const config = chartConfig || autoDetect();\n\n  // Selected Y columns (from series or single yAxis)\n  const selectedYColumns = useMemo(() => {\n    if (config.series?.length) return config.series.map((s) => s.column);\n    if (config.yAxis) return [config.yAxis];\n    return [];\n  }, [config]);\n\n  const handleYColumnsChange = useCallback(\n    (cols: string[]) => {\n      const series = cols.map((col, idx) => ({\n        column: col,\n        label: col,\n        color: DEFAULT_COLORS[idx % DEFAULT_COLORS.length],\n      }));\n      updateConfig({\n        series: series.length > 1 ? series : undefined,\n        yAxis: series.length === 1 ? cols[0] : undefined,\n      });\n    },\n    [updateConfig]\n  );\n\n  // Transform data based on configuration\n  const transformedData = useMemo(() => {\n    return transformData(result, config.transform, config.xAxis, config.yAxis || config.series);\n  }, [result, config.transform, config.xAxis, config.yAxis, config.series]);\n\n  const handleExportPNG = async () => {\n    if (!chartRef.current) {\n      toast.error(\"No chart to export\");\n      return;\n    }\n    try {\n      const fileName = `chart-${Date.now()}.png`;\n      const bg = theme === \"dark\" ? \"#1a1a1a\" : \"#ffffff\";\n      await exportChartAsPNG(chartRef.current, fileName, bg);\n      toast.success(\"Chart exported as PNG\");\n    } catch (error) {\n      toast.error(\n        `Failed to export chart: ${error instanceof Error ? error.message : \"Unknown error\"}`\n      );\n    }\n  };\n\n  // ── Build uPlot options + data ──────────────────────────────────────────────\n\n  const { uPlotOptions, uPlotData, seriesInfo } = useMemo(() => {\n    if (!config.xAxis || (!config.yAxis && (!config.series || config.series.length === 0))) {\n      return { uPlotOptions: null, uPlotData: null, seriesInfo: [] };\n    }\n\n    const chartData = transformedData.map((row) => {\n      const newRow: Record<string, unknown> = { ...row };\n      if (config.yAxis) newRow[config.yAxis] = Number(row[config.yAxis]) || 0;\n      if (config.series) {\n        config.series.forEach((s) => {\n          newRow[s.column] = Number(row[s.column]) || 0;\n        });\n      }\n      return newRow;\n    });\n\n    const yKeys: string[] =\n      config.series?.map((s) => s.column) ?? (config.yAxis ? [config.yAxis] : []);\n    const colors = config.colors || DEFAULT_COLORS;\n    const isDark = theme === \"dark\";\n\n    const xs = chartData.map((_, i) => i);\n    const seriesData = yKeys.map((key) =>\n      chartData.map((row) => (Number(row[key]) || 0) as number)\n    );\n\n    const isStacked = config.type === \"stacked_bar\" || config.type === \"stacked_area\";\n    const stackedData = isStacked\n      ? seriesData.reduce<number[][]>((acc, curr) => {\n          if (acc.length === 0) return [curr];\n          const prev = acc[acc.length - 1];\n          acc.push(curr.map((v, i) => v + prev[i]));\n          return acc;\n        }, [])\n      : seriesData;\n\n    const finalSeriesData = isStacked ? stackedData : seriesData;\n\n    const isBarType = [\"bar\", \"stacked_bar\", \"grouped_bar\"].includes(config.type);\n    const isAreaType = [\"area\", \"stacked_area\"].includes(config.type);\n    const isLineType = config.type === \"line\";\n    const isScatter = config.type === \"scatter\";\n    const useSmooth = config.smooth && (isLineType || isAreaType);\n\n    const xLabels = chartData.map((row) => shortenLabel(String(row[config.xAxis])));\n\n    const barsBuilder = isBarType\n      ? uPlot.paths.bars!({\n          size: [0.6, 100],\n          radius: 0.2,\n          gap: yKeys.length > 1 && config.type === \"grouped_bar\" ? 2 : 0,\n        })\n      : undefined;\n\n    const splineBuilder = useSmooth ? uPlot.paths.spline!() : undefined;\n\n    // Build series config\n    const sInfo: { label: string; color: string }[] = [];\n    const uSeries: uPlot.Series[] = [\n      { label: config.xAxis },\n      ...yKeys.map((key, i) => {\n        const color = config.series?.[i]?.color || colors[i % colors.length];\n        const seriesLabel = config.series?.[i]?.label || key;\n        sInfo.push({ label: seriesLabel, color });\n\n        const s: uPlot.Series = {\n          label: seriesLabel,\n          stroke: color,\n          width: isBarType ? 0 : 2,\n          fill: isBarType || isAreaType ? color + (isAreaType ? \"66\" : \"cc\") : undefined,\n          points: { show: isScatter, size: isScatter ? 8 : 4 },\n          paths: isBarType\n            ? barsBuilder\n            : useSmooth\n              ? splineBuilder\n              : isScatter\n                ? () => null\n                : undefined,\n        };\n\n        if (config.type === \"grouped_bar\" && yKeys.length > 1) {\n          const groupBars = uPlot.paths.bars!({\n            size: [0.6 / yKeys.length, 100],\n            radius: 0.2,\n            align: i === 0 ? -1 : i === yKeys.length - 1 ? 1 : 0,\n          });\n          s.paths = groupBars;\n        }\n\n        return s;\n      }),\n    ];\n\n    const needsRotation = chartData.length > 10;\n    const maxLabelLen = Math.max(...xLabels.map((l) => l.length), 1);\n    const xAxisSize = needsRotation ? Math.min(120, 40 + maxLabelLen * 5) : 60;\n\n    const uAxes: uPlot.Axis[] = [\n      {\n        stroke: isDark ? \"#888\" : \"#666\",\n        grid: { stroke: isDark ? \"rgba(255,255,255,0.08)\" : \"rgba(0,0,0,0.08)\", width: 1 },\n        ticks: { stroke: isDark ? \"rgba(255,255,255,0.15)\" : \"rgba(0,0,0,0.15)\", width: 1 },\n        values: (_u: uPlot, vals: number[]) => vals.map((v) => xLabels[v] ?? \"\"),\n        gap: 8,\n        size: xAxisSize,\n        font: \"12px system-ui, sans-serif\",\n        labelFont: \"12px system-ui, sans-serif\",\n        rotate: needsRotation ? -45 : 0,\n      },\n      {\n        stroke: isDark ? \"#888\" : \"#666\",\n        grid: config.showGrid\n          ? {\n              stroke: isDark ? \"rgba(255,255,255,0.08)\" : \"rgba(0,0,0,0.08)\",\n              width: 1,\n              dash: [4, 4],\n            }\n          : { show: false },\n        ticks: { stroke: isDark ? \"rgba(255,255,255,0.15)\" : \"rgba(0,0,0,0.15)\", width: 1 },\n        values: (_u: uPlot, vals: number[]) => vals.map((v) => formatNumberWithSuffix(v)),\n        gap: 8,\n        size: 70,\n        font: \"12px system-ui, sans-serif\",\n        labelFont: \"12px system-ui, sans-serif\",\n      },\n    ];\n\n    // Data labels hook for bar charts\n    const hooks: uPlot.Plugin[\"hooks\"] = {};\n    if (config.showValues && isBarType) {\n      hooks.draw = [\n        (u: uPlot) => {\n          const ctx = u.ctx;\n          ctx.save();\n          ctx.font = \"10px system-ui, sans-serif\";\n          ctx.fillStyle = isDark ? \"#ccc\" : \"#333\";\n          ctx.textAlign = \"center\";\n          ctx.textBaseline = \"bottom\";\n\n          for (let si = 1; si < u.series.length; si++) {\n            if (!u.series[si].show) continue;\n            for (let di = 0; di < u.data[si].length; di++) {\n              const val = u.data[si][di];\n              if (val == null) continue;\n              const cx = u.valToPos(u.data[0][di]!, \"x\", true);\n              const cy = u.valToPos(val, \"y\", true);\n              ctx.fillText(formatNumberWithSuffix(val as number), cx, cy - 4);\n            }\n          }\n          ctx.restore();\n        },\n      ];\n    }\n\n    const opts: Omit<uPlot.Options, \"width\" | \"height\"> = {\n      scales: { x: { time: false } },\n      series: uSeries,\n      axes: uAxes,\n      cursor: {\n        drag: { x: false, y: false },\n        points: {\n          size: 6,\n          fill: (u: uPlot, i: number) =>\n            (typeof u.series[i].stroke === \"function\"\n              ? (u.series[i].stroke as (self: uPlot, seriesIdx: number) => string)(u, i)\n              : u.series[i].stroke) as string,\n          stroke: \"transparent\",\n          width: 0,\n        },\n      },\n      legend: { show: false },\n      plugins: [tooltipPlugin(xLabels, { stacked: isStacked }), { hooks }],\n      padding: [16, 16, 8, 0],\n    };\n\n    const data: uPlot.AlignedData = isStacked\n      ? ([xs, ...finalSeriesData.reverse()] as uPlot.AlignedData)\n      : ([xs, ...finalSeriesData] as uPlot.AlignedData);\n\n    return { uPlotOptions: opts, uPlotData: data, seriesInfo: sInfo };\n  }, [config, transformedData, theme]);\n\n  const legendState = useMemo(\n    () => seriesInfo.map((s, i) => ({ label: s.label, show: !hiddenSeries.has(i) })),\n    [seriesInfo, hiddenSeries]\n  );\n\n  const handleLegendToggle = useCallback((idx: number) => {\n    setHiddenSeries((prev) => {\n      const next = new Set(prev);\n      if (next.has(idx)) {\n        next.delete(idx);\n      } else {\n        next.add(idx);\n      }\n      // Toggle series visibility on the uPlot instance\n      if (uPlotRef.current) {\n        uPlotRef.current.setSeries(idx + 1, { show: !next.has(idx) });\n      }\n      return next;\n    });\n  }, []);\n\n  // ── Render ──────────────────────────────────────────────────────────────────\n\n  const renderChart = () => {\n    if (config.type === \"pie\" || config.type === \"donut\") {\n      const chartData = transformedData.map((row) => ({\n        ...row,\n        [config.xAxis]: String(row[config.xAxis]),\n        [config.yAxis || config.series?.[0]?.column || \"\"]:\n          Number(row[config.yAxis || config.series?.[0]?.column || \"\"]) || 0,\n      }));\n      return (\n        <PieChartDisplay\n          data={chartData}\n          xKey={config.xAxis}\n          yKey={config.yAxis || config.series?.[0]?.column || \"\"}\n          colors={config.colors || DEFAULT_COLORS}\n          isDonut={config.type === \"donut\"}\n          innerRadius={config.innerRadius ? config.innerRadius / 120 : 0.45}\n          theme={theme}\n        />\n      );\n    }\n\n    if (!uPlotOptions || !uPlotData) return null;\n\n    return (\n      <div className=\"flex flex-col h-full\">\n        <div className=\"flex-1 min-h-0\">\n          <UPlotChart\n            options={uPlotOptions}\n            data={uPlotData}\n            className=\"w-full h-full\"\n            onInit={(u) => {\n              uPlotRef.current = u;\n            }}\n          />\n        </div>\n        <ChartLegend\n          series={legendState}\n          colors={seriesInfo.map((s) => s.color)}\n          onToggle={handleLegendToggle}\n        />\n      </div>\n    );\n  };\n\n  if (!result || !result.data || result.data.length === 0) {\n    return (\n      <div className=\"flex items-center justify-center h-full\">\n        <div className=\"text-center space-y-2\">\n          <div className=\"text-muted-foreground text-sm\">No data available for visualization</div>\n        </div>\n      </div>\n    );\n  }\n\n  const isLineOrArea = [\"line\", \"area\", \"stacked_area\"].includes(config.type);\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      {/* Compact Toolbar */}\n      <div className=\"flex flex-wrap items-center gap-2 px-4 py-2 border-b bg-muted/30\">\n        {/* Chart Type */}\n        <Select\n          value={config.type}\n          onValueChange={(value) => updateConfig({ type: value as ChartType })}\n        >\n          <SelectTrigger className=\"w-[150px] shrink-0 h-8 text-xs\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectContent>\n            {Object.entries(CHART_TYPE_INFO).map(([value, info]) => {\n              const Icon = info.icon;\n              return (\n                <SelectItem key={value} value={value}>\n                  <span className=\"flex items-center gap-2\">\n                    <Icon className=\"h-3.5 w-3.5 opacity-60\" />\n                    {info.label}\n                  </span>\n                </SelectItem>\n              );\n            })}\n          </SelectContent>\n        </Select>\n\n        {/* X-Axis */}\n        <Select value={config.xAxis} onValueChange={(value) => updateConfig({ xAxis: value })}>\n          <SelectTrigger className=\"w-[140px] shrink-0 h-8 text-xs\">\n            <span className=\"text-muted-foreground mr-1\">X:</span>\n            <SelectValue placeholder=\"Column\" />\n          </SelectTrigger>\n          <SelectContent>\n            {result.columns.map((col) => (\n              <SelectItem key={col} value={col}>\n                {col}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n\n        {/* Y-Axis */}\n        <div className=\"w-[200px] shrink-0\">\n          <MultiSelect\n            options={numericColumns.map((col, idx) => ({\n              label: col,\n              value: col,\n              color: selectedYColumns.includes(col)\n                ? DEFAULT_COLORS[selectedYColumns.indexOf(col) % DEFAULT_COLORS.length]\n                : DEFAULT_COLORS[idx % DEFAULT_COLORS.length],\n            }))}\n            selected={selectedYColumns}\n            onChange={handleYColumnsChange}\n            placeholder=\"Values...\"\n            className=\"h-8 text-xs\"\n          />\n        </div>\n\n        {/* Settings Popover */}\n        <Popover>\n          <PopoverTrigger asChild>\n            <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\">\n              <Settings2 className=\"h-4 w-4\" />\n            </Button>\n          </PopoverTrigger>\n          <PopoverContent className=\"w-[260px]\" align=\"end\">\n            <div className=\"space-y-4\">\n              <h4 className=\"font-medium text-sm\">Chart Settings</h4>\n\n              {/* Sort */}\n              <div className=\"space-y-1.5\">\n                <label className=\"text-xs text-muted-foreground flex items-center gap-1\">\n                  <ArrowUpDown className=\"h-3 w-3\" /> Sort by\n                </label>\n                <div className=\"flex gap-1.5\">\n                  <Select\n                    value={config.transform?.sortBy || \"__none__\"}\n                    onValueChange={(v) =>\n                      updateTransform({ sortBy: v === \"__none__\" ? undefined : v })\n                    }\n                  >\n                    <SelectTrigger className=\"h-8 text-xs flex-1\">\n                      <SelectValue placeholder=\"None\" />\n                    </SelectTrigger>\n                    <SelectContent>\n                      <SelectItem value=\"__none__\">None</SelectItem>\n                      {result.columns.map((col) => (\n                        <SelectItem key={col} value={col}>\n                          {col}\n                        </SelectItem>\n                      ))}\n                    </SelectContent>\n                  </Select>\n                  {config.transform?.sortBy && (\n                    <Select\n                      value={config.transform?.sortOrder || \"asc\"}\n                      onValueChange={(v) => updateTransform({ sortOrder: v as \"asc\" | \"desc\" })}\n                    >\n                      <SelectTrigger className=\"h-8 text-xs w-[80px]\">\n                        <SelectValue />\n                      </SelectTrigger>\n                      <SelectContent>\n                        <SelectItem value=\"asc\">Asc</SelectItem>\n                        <SelectItem value=\"desc\">Desc</SelectItem>\n                      </SelectContent>\n                    </Select>\n                  )}\n                </div>\n              </div>\n\n              {/* Limit */}\n              <div className=\"space-y-1.5\">\n                <label className=\"text-xs text-muted-foreground\">Limit rows</label>\n                <Input\n                  type=\"number\"\n                  min={0}\n                  placeholder=\"No limit\"\n                  className=\"h-8 text-xs\"\n                  value={config.transform?.limit ?? \"\"}\n                  onChange={(e) => {\n                    const v = e.target.value ? parseInt(e.target.value, 10) : undefined;\n                    updateTransform({ limit: v });\n                  }}\n                />\n              </div>\n\n              {/* Aggregation */}\n              <div className=\"space-y-1.5\">\n                <label className=\"text-xs text-muted-foreground\">Aggregation</label>\n                <Select\n                  value={config.transform?.aggregation || \"none\"}\n                  onValueChange={(v) =>\n                    updateTransform({\n                      aggregation: v as \"sum\" | \"avg\" | \"count\" | \"min\" | \"max\" | \"none\",\n                      groupBy: v !== \"none\" ? config.xAxis : undefined,\n                    })\n                  }\n                >\n                  <SelectTrigger className=\"h-8 text-xs\">\n                    <SelectValue />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"none\">None</SelectItem>\n                    <SelectItem value=\"sum\">Sum</SelectItem>\n                    <SelectItem value=\"avg\">Average</SelectItem>\n                    <SelectItem value=\"count\">Count</SelectItem>\n                    <SelectItem value=\"min\">Min</SelectItem>\n                    <SelectItem value=\"max\">Max</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n\n              {/* Toggles */}\n              <div className=\"space-y-3 pt-1 border-t\">\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-xs\">Show values</label>\n                  <Switch\n                    checked={config.showValues ?? false}\n                    onCheckedChange={(v) => updateConfig({ showValues: v })}\n                  />\n                </div>\n                <div className=\"flex items-center justify-between\">\n                  <label className=\"text-xs\">Show grid</label>\n                  <Switch\n                    checked={config.showGrid ?? true}\n                    onCheckedChange={(v) => updateConfig({ showGrid: v })}\n                  />\n                </div>\n                {isLineOrArea && (\n                  <div className=\"flex items-center justify-between\">\n                    <label className=\"text-xs\">Smooth lines</label>\n                    <Switch\n                      checked={config.smooth ?? false}\n                      onCheckedChange={(v) => updateConfig({ smooth: v })}\n                    />\n                  </div>\n                )}\n              </div>\n            </div>\n          </PopoverContent>\n        </Popover>\n\n        {/* Export */}\n        <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" onClick={handleExportPNG}>\n          <Download className=\"h-4 w-4\" />\n        </Button>\n\n        {/* Clear / Reset */}\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"h-8 w-8\"\n          onClick={() => {\n            onConfigChange(undefined);\n            // Will auto-detect on next render\n            setTimeout(() => onConfigChange(autoDetect()), 0);\n          }}\n          title=\"Reset chart\"\n        >\n          <RotateCcw className=\"h-3.5 w-3.5\" />\n        </Button>\n      </div>\n\n      {/* Chart Display */}\n      <div className=\"flex-1 min-h-0 p-2\" ref={chartRef}>\n        {renderChart()}\n      </div>\n    </div>\n  );\n};\n\nexport default React.memo(ChartVisualizationPro);\n"
  },
  {
    "path": "src/components/charts/UPlotChart.tsx",
    "content": "import { useRef, useEffect, useCallback } from \"react\";\nimport uPlot from \"uplot\";\nimport \"uplot/dist/uPlot.min.css\";\n\ninterface UPlotChartProps {\n  options: Omit<uPlot.Options, \"width\" | \"height\">;\n  data: uPlot.AlignedData;\n  className?: string;\n  onInit?: (chart: uPlot) => void;\n}\n\nexport default function UPlotChart({ options, data, className, onInit }: UPlotChartProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const chartRef = useRef<uPlot | null>(null);\n\n  const create = useCallback(() => {\n    if (!containerRef.current) return;\n    const el = containerRef.current;\n    const { width, height } = el.getBoundingClientRect();\n    if (width === 0 || height === 0) return;\n\n    chartRef.current?.destroy();\n    const chart = new uPlot({ ...options, width, height }, data, el);\n    chartRef.current = chart;\n    onInit?.(chart);\n  }, [options, data, onInit]);\n\n  useEffect(() => {\n    create();\n\n    const el = containerRef.current;\n    if (!el) return;\n\n    const ro = new ResizeObserver((entries) => {\n      const { width: w, height: h } = entries[0].contentRect;\n      if (w > 0 && h > 0) {\n        chartRef.current?.setSize({ width: w, height: h });\n      }\n    });\n    ro.observe(el);\n\n    return () => {\n      ro.disconnect();\n      chartRef.current?.destroy();\n      chartRef.current = null;\n    };\n  }, [create]);\n\n  return <div ref={containerRef} className={className} style={{ minHeight: 0 }} />;\n}\n"
  },
  {
    "path": "src/components/charts/tooltipPlugin.ts",
    "content": "import uPlot from \"uplot\";\nimport { formatNumberWithSuffix } from \"@/lib/chartUtils\";\n\nfunction escapeHtml(str: string): string {\n  return str\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&#039;\");\n}\n\nexport interface TooltipPluginOptions {\n  stacked?: boolean;\n}\n\nexport function tooltipPlugin(xLabels: string[], opts?: TooltipPluginOptions): uPlot.Plugin {\n  let tooltip: HTMLDivElement;\n  let over: HTMLElement;\n  const isStacked = opts?.stacked ?? false;\n\n  return {\n    hooks: {\n      init(u: uPlot) {\n        over = u.over;\n\n        tooltip = document.createElement(\"div\");\n        tooltip.className = \"uplot-tooltip\";\n        tooltip.style.display = \"none\";\n        tooltip.style.position = \"absolute\";\n        tooltip.style.pointerEvents = \"none\";\n        tooltip.style.zIndex = \"100\";\n        over.appendChild(tooltip);\n\n        over.addEventListener(\"mouseenter\", () => {\n          tooltip.style.display = \"block\";\n        });\n        over.addEventListener(\"mouseleave\", () => {\n          tooltip.style.display = \"none\";\n        });\n      },\n\n      setSize() {\n        // bounds recalculated in setCursor via over.clientWidth/Height\n      },\n\n      setCursor(u: uPlot) {\n        const { idx, left, top } = u.cursor;\n        if (idx == null || left == null || top == null) {\n          tooltip.style.display = \"none\";\n          return;\n        }\n\n        const xLabel = escapeHtml(xLabels[idx] ?? String(u.data[0][idx]));\n        let html = `<div class=\"uplot-tooltip-title\">${xLabel}</div>`;\n\n        let total = 0;\n        const rows: { label: string; color: string; val: number | null }[] = [];\n\n        for (let i = 1; i < u.series.length; i++) {\n          const s = u.series[i];\n          if (!s.show) continue;\n          const rawVal = u.data[i][idx];\n          const val = rawVal != null ? (rawVal as number) : null;\n          const color =\n            typeof s.stroke === \"function\"\n              ? (s.stroke as (self: uPlot, seriesIdx: number) => string)(u, i)\n              : s.stroke;\n          const safeLabel = typeof s.label === \"string\" ? escapeHtml(s.label) : \"\";\n          const safeColor = typeof color === \"string\" ? escapeHtml(color) : \"\";\n\n          if (val != null) total += val;\n          rows.push({ label: safeLabel, color: safeColor, val });\n        }\n\n        for (const row of rows) {\n          const formatted = row.val != null ? formatNumberWithSuffix(row.val) : \"\\u2014\";\n          const pct =\n            isStacked && row.val != null && total > 0\n              ? ` <span class=\"uplot-tooltip-pct\">(${((row.val / total) * 100).toFixed(1)}%)</span>`\n              : \"\";\n          html += `<div class=\"uplot-tooltip-row\">\n            <span class=\"uplot-tooltip-dot\" style=\"background:${row.color}\"></span>\n            <span class=\"uplot-tooltip-label\">${row.label}</span>\n            <span class=\"uplot-tooltip-value\">${formatted}${pct}</span>\n          </div>`;\n        }\n\n        if (isStacked && rows.length > 1) {\n          html += `<div class=\"uplot-tooltip-total\">\n            <span class=\"uplot-tooltip-label\">Total</span>\n            <span class=\"uplot-tooltip-value\">${formatNumberWithSuffix(total)}</span>\n          </div>`;\n        }\n\n        tooltip.innerHTML = html;\n\n        const tooltipW = tooltip.offsetWidth;\n        const tooltipH = tooltip.offsetHeight;\n        const overW = over.clientWidth;\n        const overH = over.clientHeight;\n        const pad = 12;\n\n        let posLeft = left + pad;\n        let posTop = top - tooltipH / 2;\n\n        if (posLeft + tooltipW > overW) posLeft = left - tooltipW - pad;\n        if (posTop < 0) posTop = 0;\n        if (posTop + tooltipH > overH) posTop = overH - tooltipH;\n\n        tooltip.style.left = posLeft + \"px\";\n        tooltip.style.top = posTop + \"px\";\n        tooltip.style.display = \"block\";\n      },\n    },\n  };\n}\n"
  },
  {
    "path": "src/components/cloud/CloudBrowser.tsx",
    "content": "/**\n * Cloud Browser Component\n * Displays cloud storage connections and allows browsing/importing files\n */\n\nimport { useState, useEffect } from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n  DropdownMenuSeparator,\n} from \"@/components/ui/dropdown-menu\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport {\n  Cloud,\n  CloudOff,\n  Plus,\n  MoreVertical,\n  Trash2,\n  Link,\n  Unlink,\n  AlertCircle,\n  Loader2,\n} from \"lucide-react\";\nimport { CloudConnectionModal } from \"./CloudConnectionModal\";\nimport type { CloudConnection } from \"@/store\";\n\n// Provider icons/colors\nconst PROVIDER_CONFIG = {\n  s3: { label: \"S3\", color: \"text-orange-500\", bgColor: \"bg-orange-500/10\" },\n  gcs: { label: \"GCS\", color: \"text-blue-500\", bgColor: \"bg-blue-500/10\" },\n  azure: { label: \"Azure\", color: \"text-cyan-500\", bgColor: \"bg-cyan-500/10\" },\n};\n\ninterface CloudConnectionItemProps {\n  connection: CloudConnection;\n  onConnect: () => void;\n  onDisconnect: () => void;\n  onRemove: () => void;\n}\n\nfunction CloudConnectionItem({\n  connection,\n  onConnect,\n  onDisconnect,\n  onRemove,\n}: CloudConnectionItemProps) {\n  const [isLoading, setIsLoading] = useState(false);\n  const config = PROVIDER_CONFIG[connection.type];\n\n  const handleConnect = async () => {\n    setIsLoading(true);\n    try {\n      await onConnect();\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleDisconnect = async () => {\n    setIsLoading(true);\n    try {\n      await onDisconnect();\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"group flex items-center justify-between py-1.5 px-2 rounded hover:bg-muted/50\">\n      <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n        <div className={`p-1 rounded ${config.bgColor}`}>\n          <Cloud className={`h-3.5 w-3.5 ${config.color}`} />\n        </div>\n        <div className=\"min-w-0 flex-1\">\n          <div className=\"flex items-center gap-1.5\">\n            <span className=\"text-sm truncate\">{connection.name}</span>\n            <span className={`text-[10px] px-1 py-0.5 rounded ${config.bgColor} ${config.color}`}>\n              {config.label}\n            </span>\n            {connection.isConnected && <span className=\"w-1.5 h-1.5 rounded-full bg-green-500\" />}\n          </div>\n          {connection.lastError && (\n            <TooltipProvider>\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <div className=\"flex items-center gap-1 text-destructive\">\n                    <AlertCircle className=\"h-3 w-3\" />\n                    <span className=\"text-xs truncate\">Error</span>\n                  </div>\n                </TooltipTrigger>\n                <TooltipContent side=\"right\" className=\"max-w-xs\">\n                  <p className=\"text-xs\">{connection.lastError}</p>\n                </TooltipContent>\n              </Tooltip>\n            </TooltipProvider>\n          )}\n        </div>\n      </div>\n\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 opacity-0 group-hover:opacity-100\">\n            {isLoading ? (\n              <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n            ) : (\n              <MoreVertical className=\"h-3.5 w-3.5\" />\n            )}\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"end\">\n          {connection.isConnected ? (\n            <DropdownMenuItem onClick={handleDisconnect}>\n              <Unlink className=\"h-4 w-4 mr-2\" />\n              Disconnect\n            </DropdownMenuItem>\n          ) : (\n            <DropdownMenuItem onClick={handleConnect}>\n              <Link className=\"h-4 w-4 mr-2\" />\n              Connect\n            </DropdownMenuItem>\n          )}\n          <DropdownMenuSeparator />\n          <DropdownMenuItem onClick={onRemove} className=\"text-destructive\">\n            <Trash2 className=\"h-4 w-4 mr-2\" />\n            Remove\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  );\n}\n\nexport default function CloudBrowser() {\n  const [isModalOpen, setIsModalOpen] = useState(false);\n\n  const cloudConnections = useDuckStore((s) => s.cloudConnections);\n  const cloudSupportStatus = useDuckStore((s) => s.cloudSupportStatus);\n  const isCloudStorageInitialized = useDuckStore((s) => s.isCloudStorageInitialized);\n  const initCloudStorage = useDuckStore((s) => s.initCloudStorage);\n  const connectCloudStorage = useDuckStore((s) => s.connectCloudStorage);\n  const disconnectCloudStorage = useDuckStore((s) => s.disconnectCloudStorage);\n  const removeCloudConnection = useDuckStore((s) => s.removeCloudConnection);\n\n  // Initialize cloud storage on mount\n  useEffect(() => {\n    if (!isCloudStorageInitialized) {\n      initCloudStorage();\n    }\n  }, [isCloudStorageInitialized, initCloudStorage]);\n\n  const handleConnect = async (id: string) => {\n    await connectCloudStorage(id);\n  };\n\n  const handleDisconnect = async (id: string) => {\n    await disconnectCloudStorage(id);\n  };\n\n  const handleRemove = async (id: string) => {\n    await removeCloudConnection(id);\n  };\n\n  // Show warning if cloud storage is not supported\n  const showWarning = cloudSupportStatus && !cloudSupportStatus.httpfsAvailable;\n\n  return (\n    <div className=\"space-y-2\">\n      {/* Header */}\n      <div className=\"flex items-center justify-between px-2\">\n        <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n          Cloud\n        </span>\n        <TooltipProvider>\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"h-5 w-5\"\n                onClick={() => setIsModalOpen(true)}\n              >\n                <Plus className=\"h-3.5 w-3.5\" />\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent>Add Cloud Connection</TooltipContent>\n          </Tooltip>\n        </TooltipProvider>\n      </div>\n\n      {/* Warning Banner */}\n      {showWarning && (\n        <div className=\"mx-2 p-2 rounded bg-yellow-500/10 border border-yellow-500/20\">\n          <div className=\"flex items-start gap-2\">\n            <CloudOff className=\"h-4 w-4 text-yellow-500 mt-0.5 shrink-0\" />\n            <p className=\"text-xs text-yellow-600 dark:text-yellow-400\">\n              Cloud storage has limited support in browsers. Consider using HTTPS URLs instead.\n            </p>\n          </div>\n        </div>\n      )}\n\n      {/* Connections List */}\n      {cloudConnections.length === 0 ? (\n        <div className=\"px-2 py-4 text-center\">\n          <Cloud className=\"h-8 w-8 mx-auto text-muted-foreground/30 mb-2\" />\n          <p className=\"text-xs text-muted-foreground\">No cloud connections</p>\n          <Button\n            variant=\"link\"\n            size=\"sm\"\n            className=\"text-xs h-auto p-0 mt-1\"\n            onClick={() => setIsModalOpen(true)}\n          >\n            Add your first connection\n          </Button>\n        </div>\n      ) : (\n        <div className=\"space-y-0.5\">\n          {cloudConnections.map((conn) => (\n            <CloudConnectionItem\n              key={conn.id}\n              connection={conn}\n              onConnect={() => handleConnect(conn.id)}\n              onDisconnect={() => handleDisconnect(conn.id)}\n              onRemove={() => handleRemove(conn.id)}\n            />\n          ))}\n        </div>\n      )}\n\n      {/* Add Connection Modal */}\n      <CloudConnectionModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/cloud/CloudConnectionModal.tsx",
    "content": "/**\n * Cloud Connection Modal\n * Form for adding/editing S3, GCS, and Azure cloud storage connections\n */\n\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { z } from \"zod\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@/components/ui/form\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Loader2, Cloud, AlertTriangle } from \"lucide-react\";\nimport { useDuckStore } from \"@/store\";\nimport type { CloudConnection } from \"@/store\";\n\ninterface CloudConnectionModalProps {\n  isOpen: boolean;\n  onClose: () => void;\n  existingConnection?: CloudConnection;\n}\n\n// Form validation schema\nconst cloudConnectionSchema = z\n  .object({\n    name: z.string().min(1, \"Name is required\"),\n    type: z.enum([\"s3\", \"gcs\", \"azure\"]),\n    // S3 fields\n    bucket: z.string().optional(),\n    region: z.string().optional(),\n    accessKeyId: z.string().optional(),\n    secretAccessKey: z.string().optional(),\n    endpoint: z.string().optional(),\n    // GCS fields\n    hmacKeyId: z.string().optional(),\n    hmacSecret: z.string().optional(),\n    // Azure fields\n    accountName: z.string().optional(),\n    accountKey: z.string().optional(),\n    containerName: z.string().optional(),\n  })\n  .refine(\n    (data) => {\n      // Validate required fields based on type\n      if (data.type === \"s3\") {\n        return data.bucket && data.accessKeyId && data.secretAccessKey;\n      }\n      if (data.type === \"gcs\") {\n        return data.bucket && data.hmacKeyId && data.hmacSecret;\n      }\n      if (data.type === \"azure\") {\n        return data.containerName && data.accountName && data.accountKey;\n      }\n      return true;\n    },\n    {\n      message: \"Please fill in all required fields for the selected provider\",\n    }\n  );\n\ntype CloudConnectionFormData = z.infer<typeof cloudConnectionSchema>;\n\nexport function CloudConnectionModal({\n  isOpen,\n  onClose,\n  existingConnection,\n}: CloudConnectionModalProps) {\n  const [isSubmitting, setIsSubmitting] = useState(false);\n  const [isTesting, setIsTesting] = useState(false);\n  const [testResult, setTestResult] = useState<{ success: boolean; error?: string } | null>(null);\n\n  const addCloudConnection = useDuckStore((s) => s.addCloudConnection);\n  const cloudSupportStatus = useDuckStore((s) => s.cloudSupportStatus);\n\n  const form = useForm<CloudConnectionFormData>({\n    resolver: zodResolver(cloudConnectionSchema),\n    defaultValues: {\n      name: existingConnection?.name || \"\",\n      type: existingConnection?.type || \"s3\",\n      bucket: existingConnection?.bucket || \"\",\n      region: existingConnection?.region || \"us-east-1\",\n      accessKeyId: existingConnection?.accessKeyId || \"\",\n      secretAccessKey: existingConnection?.secretAccessKey || \"\",\n      endpoint: existingConnection?.endpoint || \"\",\n      hmacKeyId: existingConnection?.hmacKeyId || \"\",\n      hmacSecret: existingConnection?.hmacSecret || \"\",\n      accountName: existingConnection?.accountName || \"\",\n      accountKey: existingConnection?.accountKey || \"\",\n      containerName: existingConnection?.containerName || \"\",\n    },\n  });\n\n  const selectedType = form.watch(\"type\");\n\n  const onSubmit = async (data: CloudConnectionFormData) => {\n    setIsSubmitting(true);\n    try {\n      await addCloudConnection({\n        name: data.name,\n        type: data.type,\n        bucket: data.bucket,\n        region: data.region,\n        accessKeyId: data.accessKeyId,\n        secretAccessKey: data.secretAccessKey,\n        endpoint: data.endpoint,\n        hmacKeyId: data.hmacKeyId,\n        hmacSecret: data.hmacSecret,\n        accountName: data.accountName,\n        accountKey: data.accountKey,\n        containerName: data.containerName,\n      });\n      onClose();\n    } finally {\n      setIsSubmitting(false);\n    }\n  };\n\n  const handleTest = async () => {\n    setIsTesting(true);\n    setTestResult(null);\n\n    // For testing, we'd need to add the connection first, test it, then remove if failed\n    // For now, show a message about the support status\n    setTimeout(() => {\n      if (!cloudSupportStatus?.httpfsAvailable) {\n        setTestResult({\n          success: false,\n          error:\n            \"Cloud storage (httpfs) is not available in this browser. Direct S3/GCS/Azure access may not work due to CORS restrictions.\",\n        });\n      } else if (!cloudSupportStatus?.secretsSupported) {\n        setTestResult({\n          success: false,\n          error: \"DuckDB secrets are not supported. Cloud storage authentication may not work.\",\n        });\n      } else {\n        setTestResult({\n          success: true,\n        });\n      }\n      setIsTesting(false);\n    }, 500);\n  };\n\n  return (\n    <Dialog open={isOpen} onOpenChange={onClose}>\n      <DialogContent className=\"sm:max-w-[500px]\">\n        <DialogHeader>\n          <DialogTitle className=\"flex items-center gap-2\">\n            <Cloud className=\"h-5 w-5\" />\n            {existingConnection ? \"Edit\" : \"Add\"} Cloud Connection\n          </DialogTitle>\n          <DialogDescription>\n            Connect to cloud storage (S3, Google Cloud Storage, or Azure Blob Storage)\n          </DialogDescription>\n        </DialogHeader>\n\n        {/* Support Status Warning */}\n        {cloudSupportStatus && !cloudSupportStatus.httpfsAvailable && (\n          <Alert variant=\"destructive\">\n            <AlertTriangle className=\"h-4 w-4\" />\n            <AlertDescription>\n              Cloud storage support is limited in this browser. The httpfs extension is not\n              available. You may need to use HTTPS URLs directly instead of s3:// or gcs://\n              protocols.\n            </AlertDescription>\n          </Alert>\n        )}\n\n        <Form {...form}>\n          <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n            {/* Connection Name */}\n            <FormField\n              control={form.control}\n              name=\"name\"\n              render={({ field }) => (\n                <FormItem>\n                  <FormLabel>Connection Name</FormLabel>\n                  <FormControl>\n                    <Input placeholder=\"My S3 Bucket\" {...field} />\n                  </FormControl>\n                  <FormMessage />\n                </FormItem>\n              )}\n            />\n\n            {/* Provider Type */}\n            <FormField\n              control={form.control}\n              name=\"type\"\n              render={({ field }) => (\n                <FormItem>\n                  <FormLabel>Provider</FormLabel>\n                  <Select onValueChange={field.onChange} defaultValue={field.value}>\n                    <FormControl>\n                      <SelectTrigger>\n                        <SelectValue placeholder=\"Select provider\" />\n                      </SelectTrigger>\n                    </FormControl>\n                    <SelectContent>\n                      <SelectItem value=\"s3\">Amazon S3 / S3-Compatible</SelectItem>\n                      <SelectItem value=\"gcs\">Google Cloud Storage</SelectItem>\n                      <SelectItem value=\"azure\">Azure Blob Storage</SelectItem>\n                    </SelectContent>\n                  </Select>\n                  <FormDescription>\n                    S3-compatible includes MinIO, Cloudflare R2, DigitalOcean Spaces\n                  </FormDescription>\n                  <FormMessage />\n                </FormItem>\n              )}\n            />\n\n            {/* S3 Fields */}\n            {selectedType === \"s3\" && (\n              <>\n                <FormField\n                  control={form.control}\n                  name=\"bucket\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Bucket Name *</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"my-bucket\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"region\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Region</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"us-east-1\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"accessKeyId\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Access Key ID *</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"AKIAIOSFODNN7EXAMPLE\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"secretAccessKey\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Secret Access Key *</FormLabel>\n                      <FormControl>\n                        <Input type=\"password\" placeholder=\"wJalrXUtn...\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"endpoint\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Custom Endpoint (Optional)</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"https://minio.example.com\" {...field} />\n                      </FormControl>\n                      <FormDescription>For S3-compatible services like MinIO or R2</FormDescription>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n              </>\n            )}\n\n            {/* GCS Fields */}\n            {selectedType === \"gcs\" && (\n              <>\n                <FormField\n                  control={form.control}\n                  name=\"bucket\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Bucket Name *</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"my-gcs-bucket\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"hmacKeyId\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>HMAC Key ID *</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"GOOGTS7C7FUP...\" {...field} />\n                      </FormControl>\n                      <FormDescription>\n                        Generate HMAC keys in GCP Console under Cloud Storage Settings\n                      </FormDescription>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"hmacSecret\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>HMAC Secret *</FormLabel>\n                      <FormControl>\n                        <Input type=\"password\" placeholder=\"...\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n              </>\n            )}\n\n            {/* Azure Fields */}\n            {selectedType === \"azure\" && (\n              <>\n                <FormField\n                  control={form.control}\n                  name=\"containerName\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Container Name *</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"my-container\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"accountName\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Storage Account Name *</FormLabel>\n                      <FormControl>\n                        <Input placeholder=\"mystorageaccount\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n\n                <FormField\n                  control={form.control}\n                  name=\"accountKey\"\n                  render={({ field }) => (\n                    <FormItem>\n                      <FormLabel>Account Key *</FormLabel>\n                      <FormControl>\n                        <Input type=\"password\" placeholder=\"...\" {...field} />\n                      </FormControl>\n                      <FormMessage />\n                    </FormItem>\n                  )}\n                />\n              </>\n            )}\n\n            {/* Test Result */}\n            {testResult && (\n              <Alert variant={testResult.success ? \"default\" : \"destructive\"}>\n                <AlertDescription>\n                  {testResult.success ? \"Cloud storage support is available!\" : testResult.error}\n                </AlertDescription>\n              </Alert>\n            )}\n\n            <DialogFooter className=\"gap-2\">\n              <Button type=\"button\" variant=\"outline\" onClick={handleTest} disabled={isTesting}>\n                {isTesting && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n                Test Support\n              </Button>\n              <Button type=\"submit\" disabled={isSubmitting}>\n                {isSubmitting && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n                {existingConnection ? \"Update\" : \"Add\"} Connection\n              </Button>\n            </DialogFooter>\n          </form>\n        </Form>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "src/components/command-palette/CommandPalette.tsx",
    "content": "import { useEffect, useState, useMemo } from \"react\";\nimport {\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n  CommandShortcut,\n} from \"@/components/ui/command\";\nimport { useDuckStore } from \"@/store\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport {\n  Terminal,\n  Home,\n  Cable,\n  Brain,\n  Settings,\n  Sun,\n  Moon,\n  Database,\n  Table,\n  Bookmark,\n} from \"lucide-react\";\nimport type { EditorTabType } from \"@/store\";\nimport {\n  getSavedQueries,\n  type SavedQuery,\n} from \"@/services/persistence/repositories/savedQueryRepository\";\n\nexport default function CommandPalette() {\n  const [open, setOpen] = useState(false);\n  const { theme, setTheme } = useTheme();\n\n  const tabs = useDuckStore((s) => s.tabs);\n  const activeTabId = useDuckStore((s) => s.activeTabId);\n  const createTab = useDuckStore((s) => s.createTab);\n  const setActiveTab = useDuckStore((s) => s.setActiveTab);\n  const toggleBrainPanel = useDuckStore((s) => s.toggleBrainPanel);\n  const databases = useDuckStore((s) => s.databases);\n  const connectionList = useDuckStore((s) => s.connectionList);\n  const currentConnection = useDuckStore((s) => s.currentConnection);\n  const setCurrentConnection = useDuckStore((s) => s.setCurrentConnection);\n  const currentProfileId = useDuckStore((s) => s.currentProfileId);\n  const savedQueriesVersion = useDuckStore((s) => s.savedQueriesVersion);\n\n  const [savedQueries, setSavedQueries] = useState<SavedQuery[]>([]);\n\n  // Load saved queries when palette opens\n  useEffect(() => {\n    if (open && currentProfileId) {\n      getSavedQueries(currentProfileId)\n        .then(setSavedQueries)\n        .catch(() => {});\n    }\n  }, [open, currentProfileId, savedQueriesVersion]);\n\n  // Keyboard shortcut\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n        e.preventDefault();\n        setOpen((o) => !o);\n      }\n    };\n    document.addEventListener(\"keydown\", handleKeyDown);\n    return () => document.removeEventListener(\"keydown\", handleKeyDown);\n  }, []);\n\n  const openOrFocusTab = (type: EditorTabType, title: string) => {\n    const existing = tabs.find((t) => t.type === type);\n    if (existing) {\n      setActiveTab(existing.id);\n    } else {\n      createTab(type, \"\", title);\n    }\n    setOpen(false);\n  };\n\n  const openTabs = useMemo(() => tabs.filter((t) => t.id !== activeTabId), [tabs, activeTabId]);\n\n  const tableEntries = useMemo(() => {\n    const entries: { database: string; table: string }[] = [];\n    for (const db of databases) {\n      for (const table of db.tables) {\n        entries.push({ database: db.name, table: table.name });\n      }\n    }\n    return entries;\n  }, [databases]);\n\n  return (\n    <CommandDialog open={open} onOpenChange={setOpen}>\n      <CommandInput placeholder=\"Search commands, tabs, tables...\" />\n      <CommandList>\n        <CommandEmpty>No results found.</CommandEmpty>\n\n        {/* Quick Actions */}\n        <CommandGroup heading=\"Quick Actions\">\n          <CommandItem\n            onSelect={() => {\n              createTab(\"sql\", \"\");\n              setOpen(false);\n            }}\n          >\n            <Terminal className=\"mr-2 h-4 w-4\" />\n            New SQL Tab\n          </CommandItem>\n          <CommandItem onSelect={() => openOrFocusTab(\"home\", \"Home\")}>\n            <Home className=\"mr-2 h-4 w-4\" />\n            Home\n          </CommandItem>\n          <CommandItem onSelect={() => openOrFocusTab(\"connections\", \"Connections\")}>\n            <Cable className=\"mr-2 h-4 w-4\" />\n            Connections\n          </CommandItem>\n          <CommandItem onSelect={() => openOrFocusTab(\"brain\", \"Duck Brain\")}>\n            <Brain className=\"mr-2 h-4 w-4\" />\n            Duck Brain\n          </CommandItem>\n          <CommandItem onSelect={() => openOrFocusTab(\"settings\", \"Settings\")}>\n            <Settings className=\"mr-2 h-4 w-4\" />\n            Settings\n          </CommandItem>\n          <CommandItem\n            onSelect={() => {\n              toggleBrainPanel();\n              setOpen(false);\n            }}\n          >\n            <Brain className=\"mr-2 h-4 w-4\" />\n            Toggle AI Panel\n          </CommandItem>\n          <CommandItem\n            onSelect={() => {\n              setTheme(theme === \"dark\" ? \"light\" : \"dark\");\n              setOpen(false);\n            }}\n          >\n            {theme === \"dark\" ? (\n              <Sun className=\"mr-2 h-4 w-4\" />\n            ) : (\n              <Moon className=\"mr-2 h-4 w-4\" />\n            )}\n            {theme === \"dark\" ? \"Light Theme\" : \"Dark Theme\"}\n          </CommandItem>\n        </CommandGroup>\n\n        {/* Open Tabs */}\n        {openTabs.length > 0 && (\n          <>\n            <CommandSeparator />\n            <CommandGroup heading=\"Open Tabs\">\n              {openTabs.map((tab) => (\n                <CommandItem\n                  key={tab.id}\n                  onSelect={() => {\n                    setActiveTab(tab.id);\n                    setOpen(false);\n                  }}\n                >\n                  <Terminal className=\"mr-2 h-4 w-4\" />\n                  {tab.title || tab.type}\n                  <CommandShortcut>{tab.type}</CommandShortcut>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </>\n        )}\n\n        {/* Connections */}\n        {connectionList.connections.length > 1 && (\n          <>\n            <CommandSeparator />\n            <CommandGroup heading=\"Connections\">\n              {connectionList.connections\n                .filter((c) => c.id !== currentConnection?.id)\n                .map((conn) => (\n                  <CommandItem\n                    key={conn.id}\n                    onSelect={async () => {\n                      await setCurrentConnection(conn.id);\n                      setOpen(false);\n                    }}\n                  >\n                    <Database className=\"mr-2 h-4 w-4\" />\n                    Switch to {conn.name}\n                    <CommandShortcut>{conn.scope}</CommandShortcut>\n                  </CommandItem>\n                ))}\n            </CommandGroup>\n          </>\n        )}\n\n        {/* Saved Queries */}\n        {savedQueries.length > 0 && (\n          <>\n            <CommandSeparator />\n            <CommandGroup heading=\"Saved Queries\">\n              {savedQueries.map((query) => (\n                <CommandItem\n                  key={query.id}\n                  onSelect={() => {\n                    createTab(\"sql\", query.sql_text, query.name);\n                    setOpen(false);\n                  }}\n                >\n                  <Bookmark className=\"mr-2 h-4 w-4\" />\n                  {query.name}\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </>\n        )}\n\n        {/* Tables */}\n        {tableEntries.length > 0 && (\n          <>\n            <CommandSeparator />\n            <CommandGroup heading=\"Tables\">\n              {tableEntries.map(({ database, table }) => (\n                <CommandItem\n                  key={`${database}.${table}`}\n                  onSelect={() => {\n                    const query = `SELECT * FROM \"${database}\".\"${table}\" LIMIT 100`;\n                    createTab(\"sql\", query, table);\n                    setOpen(false);\n                  }}\n                >\n                  <Table className=\"mr-2 h-4 w-4\" />\n                  {database}.{table}\n                  <CommandShortcut>SELECT</CommandShortcut>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </>\n        )}\n      </CommandList>\n    </CommandDialog>\n  );\n}\n"
  },
  {
    "path": "src/components/common/FloatingActionButton.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { LucideIcon } from \"lucide-react\";\n\ninterface FloatingActionButtonProps {\n  onClick: () => void;\n  icon: LucideIcon;\n  label: string;\n  disabled?: boolean;\n  className?: string;\n  variant?: \"default\" | \"outline\" | \"secondary\";\n}\n\nexport const FloatingActionButton: React.FC<FloatingActionButtonProps> = ({\n  onClick,\n  icon: Icon,\n  label,\n  disabled = false,\n  className,\n  variant = \"default\",\n}) => {\n  return (\n    <Button\n      onClick={onClick}\n      disabled={disabled}\n      variant={variant}\n      size=\"lg\"\n      className={cn(\n        // Base styles\n        \"fixed bottom-6 right-6 z-50\",\n        \"h-14 px-6 rounded-full shadow-lg\",\n        \"flex items-center gap-2\",\n        // Mobile-first (show by default)\n        \"md:hidden\",\n        // Animation\n        \"transition-all duration-200\",\n        \"hover:scale-105 active:scale-95\",\n        // Shadow\n        \"shadow-[0_8px_16px_rgba(0,0,0,0.15)]\",\n        \"dark:shadow-[0_8px_16px_rgba(0,0,0,0.3)]\",\n        className\n      )}\n      aria-label={label}\n    >\n      <Icon className=\"h-5 w-5\" />\n      <span className=\"font-medium\">{label}</span>\n    </Button>\n  );\n};\n\nexport default FloatingActionButton;\n"
  },
  {
    "path": "src/components/common/ImportOptionsPopover.tsx",
    "content": "import React, { useState } from \"react\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Import, Table, Link2 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface ImportOptions {\n  tableName: string;\n  importMode: \"table\" | \"view\";\n}\n\ninterface ImportOptionsPopoverProps {\n  fileName: string;\n  onImport: (options: ImportOptions) => void;\n  children: React.ReactNode;\n  disabled?: boolean;\n}\n\n/**\n * Generate a valid table name from a filename\n */\nfunction generateTableName(fileName: string): string {\n  return fileName\n    .replace(/\\.[^.]+$/, \"\") // Remove extension\n    .replace(/[^a-zA-Z0-9_]/g, \"_\") // Replace special chars with underscore\n    .replace(/^[0-9]/, \"_$&\") // Ensure doesn't start with number\n    .replace(/_+/g, \"_\") // Collapse multiple underscores\n    .replace(/^_|_$/g, \"\"); // Trim leading/trailing underscores\n}\n\nconst ImportOptionsPopover: React.FC<ImportOptionsPopoverProps> = ({\n  fileName,\n  onImport,\n  children,\n  disabled = false,\n}) => {\n  const [open, setOpen] = useState(false);\n  const [tableName, setTableName] = useState(generateTableName(fileName));\n  const [importMode, setImportMode] = useState<\"table\" | \"view\">(\"table\");\n\n  const handleImport = () => {\n    if (!tableName.trim()) return;\n    onImport({ tableName: tableName.trim(), importMode });\n    setOpen(false);\n  };\n\n  const handleOpenChange = (isOpen: boolean) => {\n    if (isOpen) {\n      // Reset to defaults when opening\n      setTableName(generateTableName(fileName));\n      setImportMode(\"table\");\n    }\n    setOpen(isOpen);\n  };\n\n  return (\n    <Popover open={open} onOpenChange={handleOpenChange}>\n      <PopoverTrigger asChild disabled={disabled}>\n        {children}\n      </PopoverTrigger>\n      <PopoverContent className=\"w-72\" align=\"start\" side=\"right\">\n        <div className=\"space-y-4\">\n          <div className=\"space-y-2\">\n            <h4 className=\"font-medium text-sm\">Import Options</h4>\n            <p className=\"text-xs text-muted-foreground truncate\" title={fileName}>\n              {fileName}\n            </p>\n          </div>\n\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"tableName\" className=\"text-xs\">\n              Name\n            </Label>\n            <Input\n              id=\"tableName\"\n              value={tableName}\n              onChange={(e) => setTableName(e.target.value)}\n              placeholder=\"table_name\"\n              className=\"h-8 text-sm\"\n            />\n          </div>\n\n          <div className=\"space-y-2\">\n            <Label className=\"text-xs\">Mode</Label>\n            <div className=\"flex gap-1\">\n              <Button\n                type=\"button\"\n                variant={importMode === \"table\" ? \"default\" : \"outline\"}\n                size=\"sm\"\n                className={cn(\"flex-1 gap-1.5 text-xs\", importMode === \"table\" && \"bg-primary\")}\n                onClick={() => setImportMode(\"table\")}\n              >\n                <Table className=\"h-3.5 w-3.5\" />\n                Table\n              </Button>\n              <Button\n                type=\"button\"\n                variant={importMode === \"view\" ? \"default\" : \"outline\"}\n                size=\"sm\"\n                className={cn(\"flex-1 gap-1.5 text-xs\", importMode === \"view\" && \"bg-primary\")}\n                onClick={() => setImportMode(\"view\")}\n              >\n                <Link2 className=\"h-3.5 w-3.5\" />\n                View\n              </Button>\n            </div>\n            <p className=\"text-[10px] text-muted-foreground\">\n              {importMode === \"table\"\n                ? \"Copies data into DuckDB (faster queries)\"\n                : \"Links to file (fresh data, less memory)\"}\n            </p>\n          </div>\n\n          <Button\n            onClick={handleImport}\n            disabled={!tableName.trim()}\n            size=\"sm\"\n            className=\"w-full gap-1.5\"\n          >\n            <Import className=\"h-3.5 w-3.5\" />\n            {importMode === \"table\" ? \"Import\" : \"Link\"}\n          </Button>\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n};\n\nexport default ImportOptionsPopover;\n"
  },
  {
    "path": "src/components/connection/ConnectionsModal.tsx",
    "content": "// ConnectionManager.tsx\nimport React from \"react\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n  SheetFooter,\n} from \"@/components/ui/sheet\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useForm } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Button } from \"@/components/ui/button\";\nimport { useDuckStore } from \"@/store\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Info } from \"lucide-react\";\n\nconst scopeEnum = z.enum([\"External\", \"OPFS\"]);\nconst nameSchema = z\n  .string()\n  .min(2, {\n    message: \"Connection name must be at least 2 characters.\",\n  })\n  .max(30, {\n    message: \"Connection name must not exceed 30 characters.\",\n  });\n\nconst opfsSchema = z.object({\n  name: nameSchema,\n  scope: z.literal(scopeEnum.enum.OPFS),\n  path: z.string().min(1, {\n    message: \"Path is required.\",\n  }),\n});\n\nconst externalSchema = z.object({\n  name: nameSchema,\n  scope: z.literal(scopeEnum.enum.External),\n  host: z.string().url({\n    message: \"Host must be a valid URL.\",\n  }),\n  port: z\n    .string()\n    .refine((val) => !isNaN(parseInt(val, 10)) || val === \"\", {\n      message: \"Port must be a number.\",\n    })\n    .optional(),\n  database: z.string().optional(),\n  user: z.string().optional(),\n  password: z.string().optional(),\n  authMode: z.enum([\"none\", \"password\", \"api_key\"]).optional(),\n  apiKey: z.string().optional(),\n});\n\nconst connectionSchema = z.discriminatedUnion(\"scope\", [opfsSchema, externalSchema]);\n\ntype ConnectionFormValues = z.infer<typeof connectionSchema>;\n\ninterface ConnectionManagerProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  onSubmit: (values: ConnectionFormValues) => Promise<void>;\n  initialValues?: ConnectionFormValues;\n  isEditMode?: boolean;\n}\n\nconst ConnectionManager: React.FC<ConnectionManagerProps> = ({\n  open,\n  onOpenChange,\n  onSubmit,\n  initialValues,\n  isEditMode = false,\n}) => {\n  const form = useForm<ConnectionFormValues>({\n    resolver: zodResolver(connectionSchema),\n    defaultValues: initialValues || {\n      name: \"Local DuckDB\",\n      scope: \"External\" as const,\n      host: \"http://localhost:9999\",\n      port: \"\",\n      database: \"\",\n      user: \"\",\n      password: \"\",\n      authMode: \"none\" as const,\n      apiKey: \"\",\n    },\n    mode: \"onChange\",\n  });\n\n  const currentScope = form.watch(\"scope\");\n  const isLoadingExternalConnection = useDuckStore((s) => s.isLoadingExternalConnection);\n\n  const handleSubmit = async (values: ConnectionFormValues) => {\n    await onSubmit(values);\n    form.reset();\n    onOpenChange(false);\n  };\n\n  return (\n    <Sheet open={open} onOpenChange={onOpenChange}>\n      <SheetContent side=\"right\" className=\"w-full sm:w-[450px] overflow-y-auto\">\n        <SheetHeader>\n          <SheetTitle>{isEditMode ? \"Edit Connection\" : \"Add New Connection\"}</SheetTitle>\n          <SheetDescription>\n            {isEditMode\n              ? \"Modify existing connection details.\"\n              : \"Connect to a DuckDB instance or browser storage.\"}\n          </SheetDescription>\n        </SheetHeader>\n\n        <div className=\"mt-6\">\n          <Form {...form}>\n            <form onSubmit={form.handleSubmit(handleSubmit)} className=\"space-y-4\">\n              <FormField\n                control={form.control}\n                name=\"name\"\n                render={({ field }) => (\n                  <FormItem>\n                    <FormLabel>Connection Name</FormLabel>\n                    <FormControl>\n                      <Input placeholder=\"My Database\" {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n\n              <FormField\n                control={form.control}\n                name=\"scope\"\n                render={({ field }) => (\n                  <FormItem>\n                    <FormLabel>Connection Type</FormLabel>\n                    <Select onValueChange={field.onChange} defaultValue={field.value}>\n                      <FormControl>\n                        <SelectTrigger>\n                          <SelectValue placeholder=\"Select type\" />\n                        </SelectTrigger>\n                      </FormControl>\n                      <SelectContent>\n                        <SelectItem value=\"External\">DuckDB HTTP Server</SelectItem>\n                        <SelectItem value=\"OPFS\">Browser Storage (OPFS)</SelectItem>\n                      </SelectContent>\n                    </Select>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n\n              {/* External Connection Fields */}\n              {currentScope === \"External\" && (\n                <>\n                  <Alert className=\"bg-muted/50\">\n                    <Info className=\"h-4 w-4\" />\n                    <AlertDescription className=\"text-xs space-y-1\">\n                      <p>Start HTTP server in DuckDB:</p>\n                      <pre className=\"bg-background px-2 py-1 rounded text-[10px] leading-relaxed\">\n                        {`INSTALL httpserver FROM community;\nLOAD httpserver;\nSELECT httpserve_start('0.0.0.0', 9999, '');`}\n                      </pre>\n                    </AlertDescription>\n                  </Alert>\n\n                  <FormField\n                    control={form.control}\n                    name=\"host\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Host URL</FormLabel>\n                        <FormControl>\n                          <Input\n                            placeholder=\"http://localhost:9999\"\n                            {...field}\n                            value={field.value ?? \"\"}\n                          />\n                        </FormControl>\n                        <FormDescription>Full URL including protocol (http/https)</FormDescription>\n                        <FormMessage />\n                      </FormItem>\n                    )}\n                  />\n\n                  <FormField\n                    control={form.control}\n                    name=\"database\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Database (optional)</FormLabel>\n                        <FormControl>\n                          <Input placeholder=\"my_database\" {...field} value={field.value ?? \"\"} />\n                        </FormControl>\n                        <FormMessage />\n                      </FormItem>\n                    )}\n                  />\n\n                  <FormField\n                    control={form.control}\n                    name=\"authMode\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Authentication</FormLabel>\n                        <Select onValueChange={field.onChange} defaultValue={field.value}>\n                          <FormControl>\n                            <SelectTrigger>\n                              <SelectValue placeholder=\"Select auth mode\" />\n                            </SelectTrigger>\n                          </FormControl>\n                          <SelectContent>\n                            <SelectItem value=\"none\">None</SelectItem>\n                            <SelectItem value=\"password\">Username/Password</SelectItem>\n                            <SelectItem value=\"api_key\">API Key</SelectItem>\n                          </SelectContent>\n                        </Select>\n                        <FormMessage />\n                      </FormItem>\n                    )}\n                  />\n\n                  {form.watch(\"authMode\") === \"password\" && (\n                    <div className=\"grid grid-cols-2 gap-3\">\n                      <FormField\n                        control={form.control}\n                        name=\"user\"\n                        render={({ field }) => (\n                          <FormItem>\n                            <FormLabel>Username</FormLabel>\n                            <FormControl>\n                              <Input placeholder=\"user\" {...field} value={field.value ?? \"\"} />\n                            </FormControl>\n                            <FormMessage />\n                          </FormItem>\n                        )}\n                      />\n                      <FormField\n                        control={form.control}\n                        name=\"password\"\n                        render={({ field }) => (\n                          <FormItem>\n                            <FormLabel>Password</FormLabel>\n                            <FormControl>\n                              <Input\n                                type=\"password\"\n                                placeholder=\"********\"\n                                {...field}\n                                value={field.value ?? \"\"}\n                              />\n                            </FormControl>\n                            <FormMessage />\n                          </FormItem>\n                        )}\n                      />\n                    </div>\n                  )}\n\n                  {form.watch(\"authMode\") === \"api_key\" && (\n                    <FormField\n                      control={form.control}\n                      name=\"apiKey\"\n                      render={({ field }) => (\n                        <FormItem>\n                          <FormLabel>API Key</FormLabel>\n                          <FormControl>\n                            <Input\n                              type=\"password\"\n                              placeholder=\"Enter API key\"\n                              {...field}\n                              value={field.value ?? \"\"}\n                            />\n                          </FormControl>\n                          <FormMessage />\n                        </FormItem>\n                      )}\n                    />\n                  )}\n                </>\n              )}\n\n              {/* OPFS Fields */}\n              {currentScope === \"OPFS\" && (\n                <>\n                  <Alert className=\"bg-muted/50\">\n                    <Info className=\"h-4 w-4\" />\n                    <AlertDescription className=\"text-xs\">\n                      Data persists in your browser across sessions.\n                    </AlertDescription>\n                  </Alert>\n\n                  <FormField\n                    control={form.control}\n                    name=\"path\"\n                    render={({ field }) => (\n                      <FormItem>\n                        <FormLabel>Database File</FormLabel>\n                        <FormControl>\n                          <Input placeholder=\"my_data.db\" {...field} value={field.value ?? \"\"} />\n                        </FormControl>\n                        <FormDescription>\n                          Filename for your database (e.g., data.db)\n                        </FormDescription>\n                        <FormMessage />\n                      </FormItem>\n                    )}\n                  />\n                </>\n              )}\n\n              <SheetFooter className=\"pt-4\">\n                <Button\n                  type=\"button\"\n                  variant=\"outline\"\n                  onClick={() => onOpenChange(false)}\n                  disabled={isLoadingExternalConnection}\n                >\n                  Cancel\n                </Button>\n                <Button type=\"submit\" disabled={isLoadingExternalConnection}>\n                  {isLoadingExternalConnection\n                    ? \"Connecting...\"\n                    : isEditMode\n                      ? \"Update\"\n                      : \"Connect\"}\n                </Button>\n              </SheetFooter>\n            </form>\n          </Form>\n        </div>\n      </SheetContent>\n    </Sheet>\n  );\n};\n\nexport default ConnectionManager;\n"
  },
  {
    "path": "src/components/duck-brain/DuckBrainCodeBlock.tsx",
    "content": "import React from \"react\";\nimport { Copy, Play, FileInput, Check, Loader2 } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { toast } from \"sonner\";\nimport { cn } from \"@/lib/utils\";\nimport type { QueryResultArtifact } from \"@/store\";\nimport ResultsArtifact from \"./ResultsArtifact\";\n\ninterface DuckBrainCodeBlockProps {\n  sql: string;\n  messageId: string;\n  queryResult?: QueryResultArtifact;\n  onExecute?: (messageId: string, sql: string) => void;\n  onInsert?: (sql: string) => void;\n  className?: string;\n}\n\nconst DuckBrainCodeBlock: React.FC<DuckBrainCodeBlockProps> = ({\n  sql,\n  messageId,\n  queryResult,\n  onExecute,\n  onInsert,\n  className,\n}) => {\n  const [copied, setCopied] = React.useState(false);\n  const isRunning = queryResult?.status === \"running\";\n\n  const handleCopy = async () => {\n    try {\n      await navigator.clipboard.writeText(sql);\n      setCopied(true);\n      toast.success(\"SQL copied to clipboard\");\n      setTimeout(() => setCopied(false), 2000);\n    } catch {\n      toast.error(\"Failed to copy\");\n    }\n  };\n\n  const handleExecute = () => {\n    if (onExecute && !isRunning) {\n      onExecute(messageId, sql);\n    }\n  };\n\n  const handleRetry = () => {\n    handleExecute();\n  };\n\n  return (\n    <div className={cn(\"space-y-2\", className)}>\n      {/* SQL Code Block */}\n      <div className=\"rounded-lg overflow-hidden border bg-muted/30\">\n        {/* SQL Code */}\n        <pre className=\"p-3 text-xs overflow-x-auto\">\n          <code className=\"text-foreground font-mono whitespace-pre-wrap break-all\">{sql}</code>\n        </pre>\n\n        {/* Actions */}\n        <div className=\"flex items-center gap-1 p-2 border-t bg-muted/50\">\n          <Button variant=\"ghost\" size=\"sm\" onClick={handleCopy} className=\"h-7 text-xs gap-1\">\n            {copied ? (\n              <>\n                <Check className=\"h-3 w-3\" />\n                Copied\n              </>\n            ) : (\n              <>\n                <Copy className=\"h-3 w-3\" />\n                Copy\n              </>\n            )}\n          </Button>\n\n          {onInsert && (\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              onClick={() => onInsert(sql)}\n              className=\"h-7 text-xs gap-1\"\n            >\n              <FileInput className=\"h-3 w-3\" />\n              Insert\n            </Button>\n          )}\n\n          {onExecute && (\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              onClick={handleExecute}\n              disabled={isRunning}\n              className=\"h-7 text-xs gap-1 text-primary\"\n            >\n              {isRunning ? (\n                <>\n                  <Loader2 className=\"h-3 w-3 animate-spin\" />\n                  Running...\n                </>\n              ) : (\n                <>\n                  <Play className=\"h-3 w-3\" />\n                  Run\n                </>\n              )}\n            </Button>\n          )}\n        </div>\n      </div>\n\n      {/* Results Artifact */}\n      {queryResult && queryResult.status !== \"pending\" && (\n        <ResultsArtifact queryResult={queryResult} onRetry={handleRetry} />\n      )}\n    </div>\n  );\n};\n\nexport default DuckBrainCodeBlock;\n"
  },
  {
    "path": "src/components/duck-brain/DuckBrainInput.tsx",
    "content": "import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { Send, Square, Loader2, Table2, Columns } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\nimport type { DatabaseInfo } from \"@/store\";\nimport SchemaAutocomplete, {\n  buildSchemaSuggestions,\n  type SchemaSuggestion,\n} from \"./SchemaAutocomplete\";\n\n// Render text with styled @ mentions\nconst renderWithMentions = (text: string) => {\n  const mentionRegex = /@([\\w.]+)/g;\n  const parts: React.ReactNode[] = [];\n  let lastIndex = 0;\n  let match;\n\n  while ((match = mentionRegex.exec(text)) !== null) {\n    // Add text before the mention\n    if (match.index > lastIndex) {\n      parts.push(\n        <span key={`text-${lastIndex}`} className=\"whitespace-pre-wrap\">\n          {text.slice(lastIndex, match.index)}\n        </span>\n      );\n    }\n\n    // Add the mention as a styled pill\n    const mention = match[1];\n    const isColumn = mention.includes(\".\");\n    parts.push(\n      <span\n        key={`mention-${match.index}`}\n        className=\"inline-flex items-center gap-0.5 px-1 py-px rounded bg-primary/20 text-primary text-xs font-medium align-baseline\"\n      >\n        {isColumn ? <Columns className=\"h-3 w-3\" /> : <Table2 className=\"h-3 w-3\" />}\n        {mention}\n      </span>\n    );\n\n    lastIndex = match.index + match[0].length;\n  }\n\n  // Add remaining text\n  if (lastIndex < text.length) {\n    parts.push(\n      <span key={`text-${lastIndex}`} className=\"whitespace-pre-wrap\">\n        {text.slice(lastIndex)}\n      </span>\n    );\n  }\n\n  return parts.length > 0 ? parts : text;\n};\n\ninterface DuckBrainInputProps {\n  onSend: (message: string) => void;\n  onAbort?: () => void;\n  isGenerating: boolean;\n  disabled?: boolean;\n  placeholder?: string;\n  databases?: DatabaseInfo[];\n  className?: string;\n}\n\nconst DuckBrainInput: React.FC<DuckBrainInputProps> = ({\n  onSend,\n  onAbort,\n  isGenerating,\n  disabled = false,\n  placeholder = \"Ask Duck Brain to write SQL... (@ for tables)\",\n  databases = [],\n  className,\n}) => {\n  const [input, setInput] = useState(\"\");\n  const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);\n  const [suggestions, setSuggestions] = useState<SchemaSuggestion[]>([]);\n  const [activeIndex, setActiveIndex] = useState(0);\n  const [mentionStart, setMentionStart] = useState<number | null>(null);\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\n  const containerRef = useRef<HTMLFormElement>(null);\n\n  // Auto-resize textarea\n  useEffect(() => {\n    const textarea = textareaRef.current;\n    if (textarea) {\n      textarea.style.height = \"auto\";\n      textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;\n    }\n  }, [input]);\n\n  // Detect @ mentions and filter suggestions\n  const handleInputChange = useCallback(\n    (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n      const value = e.target.value;\n      const cursorPos = e.target.selectionStart || 0;\n      setInput(value);\n\n      // Find the @ before cursor\n      const textBeforeCursor = value.slice(0, cursorPos);\n      const lastAtIndex = textBeforeCursor.lastIndexOf(\"@\");\n\n      if (lastAtIndex !== -1) {\n        // Check if there's a space between @ and cursor (mention ended)\n        const textAfterAt = textBeforeCursor.slice(lastAtIndex + 1);\n        const hasSpace = /\\s/.test(textAfterAt);\n\n        if (!hasSpace) {\n          // Active mention - show suggestions\n          setMentionStart(lastAtIndex);\n          const filter = textAfterAt;\n          const filtered = buildSchemaSuggestions(databases, filter);\n          setSuggestions(filtered);\n          setIsAutocompleteOpen(filtered.length > 0);\n          setActiveIndex(0);\n          return;\n        }\n      }\n\n      // No active mention\n      setIsAutocompleteOpen(false);\n      setMentionStart(null);\n    },\n    [databases]\n  );\n\n  // Insert selected suggestion\n  const insertSuggestion = useCallback(\n    (suggestion: SchemaSuggestion) => {\n      if (mentionStart === null) return;\n\n      const before = input.slice(0, mentionStart);\n      const cursorPos = textareaRef.current?.selectionStart || input.length;\n      const after = input.slice(cursorPos);\n\n      // Insert with @ prefix so it renders as a pill\n      const insertText = `@${suggestion.fullPath}`;\n      const newInput = `${before}${insertText} ${after}`;\n\n      setInput(newInput);\n      setIsAutocompleteOpen(false);\n      setMentionStart(null);\n\n      // Focus and set cursor position\n      setTimeout(() => {\n        const newCursorPos = before.length + insertText.length + 1;\n        textareaRef.current?.focus();\n        textareaRef.current?.setSelectionRange(newCursorPos, newCursorPos);\n      }, 0);\n    },\n    [input, mentionStart]\n  );\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    // Handle autocomplete navigation\n    if (isAutocompleteOpen && suggestions.length > 0) {\n      switch (e.key) {\n        case \"ArrowDown\":\n          e.preventDefault();\n          setActiveIndex((prev) => (prev + 1) % suggestions.length);\n          return;\n        case \"ArrowUp\":\n          e.preventDefault();\n          setActiveIndex((prev) => (prev === 0 ? suggestions.length - 1 : prev - 1));\n          return;\n        case \"Tab\":\n        case \"Enter\":\n          if (suggestions[activeIndex]) {\n            e.preventDefault();\n            insertSuggestion(suggestions[activeIndex]);\n            return;\n          }\n          break;\n        case \"Escape\":\n          e.preventDefault();\n          setIsAutocompleteOpen(false);\n          return;\n      }\n    }\n\n    // Regular Enter to send (if not in autocomplete)\n    if (e.key === \"Enter\" && !e.shiftKey && !isAutocompleteOpen) {\n      e.preventDefault();\n      handleSubmit();\n    }\n  };\n\n  const handleSubmit = (e?: React.FormEvent) => {\n    e?.preventDefault();\n    if (!input.trim() || isGenerating || disabled) return;\n\n    onSend(input.trim());\n    setInput(\"\");\n    setIsAutocompleteOpen(false);\n  };\n\n  // Close autocomplete when clicking outside\n  useEffect(() => {\n    const handleClickOutside = (e: MouseEvent) => {\n      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n        setIsAutocompleteOpen(false);\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, []);\n\n  // Check if input has any @ mentions to show overlay\n  const hasMentions = /@[\\w.]+/.test(input);\n\n  return (\n    <form ref={containerRef} onSubmit={handleSubmit} className={cn(\"relative\", className)}>\n      {/* Visual overlay for styled mentions */}\n      {hasMentions && (\n        <div\n          className=\"absolute inset-0 pointer-events-none px-3 py-[9px] text-sm overflow-hidden\"\n          aria-hidden=\"true\"\n        >\n          <div className=\"whitespace-pre-wrap break-words leading-normal\">\n            {renderWithMentions(input)}\n          </div>\n        </div>\n      )}\n      <Textarea\n        ref={textareaRef}\n        value={input}\n        onChange={handleInputChange}\n        onKeyDown={handleKeyDown}\n        placeholder={placeholder}\n        disabled={disabled || isGenerating}\n        className={cn(\n          \"min-h-[44px] max-h-[120px] resize-none pr-12\",\n          \"text-sm placeholder:text-muted-foreground/60\",\n          // Make text transparent when we have mentions to show overlay\n          hasMentions && \"text-transparent caret-foreground\"\n        )}\n        rows={1}\n      />\n\n      {/* Schema Autocomplete Popover */}\n      <SchemaAutocomplete\n        isOpen={isAutocompleteOpen}\n        suggestions={suggestions}\n        activeIndex={activeIndex}\n        onSelect={insertSuggestion}\n        position={{ top: 50, left: 0 }}\n      />\n\n      <div className=\"absolute right-2 bottom-2\">\n        {isGenerating ? (\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={onAbort}\n            className=\"h-8 w-8 text-destructive hover:text-destructive\"\n          >\n            <Square className=\"h-4 w-4\" />\n          </Button>\n        ) : (\n          <Button\n            type=\"submit\"\n            variant=\"ghost\"\n            size=\"icon\"\n            disabled={!input.trim() || disabled}\n            className=\"h-8 w-8\"\n          >\n            {disabled ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Send className=\"h-4 w-4\" />}\n          </Button>\n        )}\n      </div>\n    </form>\n  );\n};\n\nexport default DuckBrainInput;\n"
  },
  {
    "path": "src/components/duck-brain/DuckBrainMessages.tsx",
    "content": "import React, { useRef, useEffect } from \"react\";\nimport { User, Bot, Table2, Columns } from \"lucide-react\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\nimport type { DuckBrainMessage } from \"@/store\";\nimport DuckBrainCodeBlock from \"./DuckBrainCodeBlock\";\nimport MarkdownContent from \"./MarkdownContent\";\n\n// Render @ mentions as styled pills\nconst renderMentions = (content: string) => {\n  // Match @table_name or @table.column patterns\n  const mentionRegex = /@([\\w.]+)/g;\n  const parts: (string | React.ReactNode)[] = [];\n  let lastIndex = 0;\n  let match;\n\n  while ((match = mentionRegex.exec(content)) !== null) {\n    // Add text before the mention\n    if (match.index > lastIndex) {\n      parts.push(content.slice(lastIndex, match.index));\n    }\n\n    // Add the mention as a pill\n    const mention = match[1];\n    const isColumn = mention.includes(\".\");\n    parts.push(\n      <span\n        key={`${match.index}-${mention}`}\n        className=\"inline-flex items-center gap-1 px-1.5 py-0.5 mx-0.5 rounded-md bg-primary/20 text-primary-foreground text-xs font-medium\"\n      >\n        {isColumn ? <Columns className=\"h-3 w-3\" /> : <Table2 className=\"h-3 w-3\" />}\n        {mention}\n      </span>\n    );\n\n    lastIndex = match.index + match[0].length;\n  }\n\n  // Add remaining text\n  if (lastIndex < content.length) {\n    parts.push(content.slice(lastIndex));\n  }\n\n  return parts.length > 0 ? parts : content;\n};\n\ninterface DuckBrainMessagesProps {\n  messages: DuckBrainMessage[];\n  streamingContent: string;\n  isGenerating: boolean;\n  onExecuteSQL?: (messageId: string, sql: string) => void;\n  onInsertSQL?: (sql: string) => void;\n  className?: string;\n}\n\nconst DuckBrainMessages: React.FC<DuckBrainMessagesProps> = ({\n  messages,\n  streamingContent,\n  isGenerating,\n  onExecuteSQL,\n  onInsertSQL,\n  className,\n}) => {\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const bottomRef = useRef<HTMLDivElement>(null);\n\n  // Auto-scroll to bottom on new messages\n  useEffect(() => {\n    bottomRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages, streamingContent]);\n\n  const formatTime = (date: Date) => {\n    return new Date(date).toLocaleTimeString([], {\n      hour: \"2-digit\",\n      minute: \"2-digit\",\n    });\n  };\n\n  if (messages.length === 0 && !isGenerating) {\n    return (\n      <div className={cn(\"flex-1 flex items-center justify-center p-4\", className)}>\n        <div className=\"text-center text-muted-foreground\">\n          <Bot className=\"h-12 w-12 mx-auto mb-3 opacity-50\" />\n          <p className=\"text-sm font-medium\">Hi! I'm Duck Brain</p>\n          <p className=\"text-xs mt-1\">Ask me to write SQL queries for your data</p>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <ScrollArea className={cn(\"flex-1\", className)} ref={scrollRef}>\n      <div className=\"p-4 space-y-4\">\n        {messages.map((message) => (\n          <div\n            key={message.id}\n            className={cn(\"flex gap-3\", message.role === \"user\" ? \"justify-end\" : \"justify-start\")}\n          >\n            {message.role === \"assistant\" && (\n              <div className=\"flex-shrink-0 w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center\">\n                <Bot className=\"h-4 w-4 text-primary\" />\n              </div>\n            )}\n\n            <div\n              className={cn(\n                \"max-w-[85%] space-y-2\",\n                message.role === \"user\" ? \"items-end\" : \"items-start\"\n              )}\n            >\n              <div\n                className={cn(\n                  \"rounded-2xl px-4 py-2\",\n                  message.role === \"user\"\n                    ? \"bg-primary text-primary-foreground rounded-br-sm\"\n                    : \"bg-muted rounded-bl-sm\"\n                )}\n              >\n                {message.role === \"user\" ? (\n                  <p className=\"text-sm whitespace-pre-wrap\">{renderMentions(message.content)}</p>\n                ) : (\n                  <MarkdownContent content={message.content} skipCodeBlocks={!!message.sql} />\n                )}\n              </div>\n\n              {/* SQL Code Block for assistant messages with extracted SQL */}\n              {message.role === \"assistant\" && message.sql && (\n                <DuckBrainCodeBlock\n                  sql={message.sql}\n                  messageId={message.id}\n                  queryResult={message.queryResult}\n                  onExecute={onExecuteSQL}\n                  onInsert={onInsertSQL}\n                />\n              )}\n\n              <span className=\"text-[10px] text-muted-foreground px-1\">\n                {formatTime(message.timestamp)}\n              </span>\n            </div>\n\n            {message.role === \"user\" && (\n              <div className=\"flex-shrink-0 w-7 h-7 rounded-full bg-muted flex items-center justify-center\">\n                <User className=\"h-4 w-4\" />\n              </div>\n            )}\n          </div>\n        ))}\n\n        {/* Streaming response */}\n        {isGenerating && streamingContent && (\n          <div className=\"flex gap-3 justify-start\">\n            <div className=\"flex-shrink-0 w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center\">\n              <Bot className=\"h-4 w-4 text-primary animate-pulse\" />\n            </div>\n            <div className=\"max-w-[85%]\">\n              <div className=\"rounded-2xl rounded-bl-sm bg-muted px-4 py-2\">\n                <p className=\"text-sm whitespace-pre-wrap\">\n                  {streamingContent}\n                  <span className=\"inline-block w-1 h-4 bg-primary ml-1 animate-pulse\" />\n                </p>\n              </div>\n            </div>\n          </div>\n        )}\n\n        {/* Loading indicator */}\n        {isGenerating && !streamingContent && (\n          <div className=\"flex gap-3 justify-start\">\n            <div className=\"flex-shrink-0 w-7 h-7 rounded-full bg-primary/10 flex items-center justify-center\">\n              <Bot className=\"h-4 w-4 text-primary\" />\n            </div>\n            <div className=\"rounded-2xl rounded-bl-sm bg-muted px-4 py-2\">\n              <div className=\"flex gap-1\">\n                <span className=\"w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce\" />\n                <span className=\"w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce [animation-delay:150ms]\" />\n                <span className=\"w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce [animation-delay:300ms]\" />\n              </div>\n            </div>\n          </div>\n        )}\n\n        <div ref={bottomRef} />\n      </div>\n    </ScrollArea>\n  );\n};\n\nexport default DuckBrainMessages;\n"
  },
  {
    "path": "src/components/duck-brain/DuckBrainPanel.tsx",
    "content": "import React, { useCallback, useMemo } from \"react\";\nimport { Brain, X, Loader2, AlertCircle, Download, Trash2, RefreshCw, Cloud } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { useDuckStore, type AIProviderType } from \"@/store\";\nimport { AVAILABLE_MODELS, DEFAULT_MODEL } from \"@/lib/duckBrain\";\nimport { OPENAI_MODELS, ANTHROPIC_MODELS } from \"@/lib/duckBrain/providers/types\";\nimport DuckBrainMessages from \"./DuckBrainMessages\";\nimport DuckBrainInput from \"./DuckBrainInput\";\nimport { toast } from \"sonner\";\n\ninterface DuckBrainPanelProps {\n  tabId: string;\n}\n\nconst DuckBrainPanel: React.FC<DuckBrainPanelProps> = React.memo(({ tabId }) => {\n  const duckBrain = useDuckStore((s) => s.duckBrain);\n  const databases = useDuckStore((s) => s.databases);\n  const toggleBrainPanel = useDuckStore((s) => s.toggleBrainPanel);\n  const initializeDuckBrain = useDuckStore((s) => s.initializeDuckBrain);\n  const generateSQL = useDuckStore((s) => s.generateSQL);\n  const abortGeneration = useDuckStore((s) => s.abortGeneration);\n  const clearBrainMessages = useDuckStore((s) => s.clearBrainMessages);\n  const executeQueryInChat = useDuckStore((s) => s.executeQueryInChat);\n  const updateTabQuery = useDuckStore((s) => s.updateTabQuery);\n  const setAIProvider = useDuckStore((s) => s.setAIProvider);\n\n  const {\n    modelStatus,\n    downloadProgress,\n    downloadStatus,\n    isWebGPUSupported,\n    error,\n    messages,\n    isGenerating,\n    streamingContent,\n    aiProvider = \"webllm\",\n    providerConfigs = {},\n  } = duckBrain;\n\n  // Get the display name for the current provider/model\n  const providerDisplayInfo = useMemo(() => {\n    if (aiProvider === \"openai\") {\n      const config = providerConfigs.openai;\n      if (config?.apiKey) {\n        const model = OPENAI_MODELS.find((m) => m.id === config.modelId);\n        return { name: model?.name || \"GPT-4o Mini\", isCloud: true };\n      }\n    } else if (aiProvider === \"anthropic\") {\n      const config = providerConfigs.anthropic;\n      if (config?.apiKey) {\n        const model = ANTHROPIC_MODELS.find((m) => m.id === config.modelId);\n        return { name: model?.name || \"Claude Sonnet 4\", isCloud: true };\n      }\n    } else if (aiProvider === \"openai-compatible\") {\n      const config = providerConfigs[\"openai-compatible\"];\n      if (config?.baseUrl && config?.modelId) {\n        return { name: config.modelId, isCloud: true };\n      }\n    }\n    // Default to local model\n    const localModel = AVAILABLE_MODELS.find((m) => m.id === duckBrain.currentModel);\n    return { name: localModel?.displayName || \"Local Model\", isCloud: false };\n  }, [aiProvider, providerConfigs, duckBrain.currentModel]);\n\n  // Build list of available providers for selector\n  const availableProviders = useMemo(() => {\n    const providers: { value: AIProviderType; label: string }[] = [];\n\n    // Add WebLLM if model is loaded or loading\n    if (modelStatus === \"ready\" || modelStatus === \"downloading\" || modelStatus === \"loading\") {\n      const localModel = AVAILABLE_MODELS.find((m) => m.id === duckBrain.currentModel);\n      providers.push({\n        value: \"webllm\",\n        label: localModel?.displayName || \"Local Model\",\n      });\n    }\n\n    // Add OpenAI if configured\n    if (providerConfigs.openai?.apiKey) {\n      const model = OPENAI_MODELS.find((m) => m.id === providerConfigs.openai?.modelId);\n      providers.push({\n        value: \"openai\",\n        label: model?.name || \"GPT-4o Mini\",\n      });\n    }\n\n    // Add Anthropic if configured\n    if (providerConfigs.anthropic?.apiKey) {\n      const model = ANTHROPIC_MODELS.find((m) => m.id === providerConfigs.anthropic?.modelId);\n      providers.push({\n        value: \"anthropic\",\n        label: model?.name || \"Claude Sonnet 4\",\n      });\n    }\n\n    // Add OpenAI-Compatible if configured\n    if (\n      providerConfigs[\"openai-compatible\"]?.baseUrl &&\n      providerConfigs[\"openai-compatible\"]?.modelId\n    ) {\n      providers.push({\n        value: \"openai-compatible\",\n        label: providerConfigs[\"openai-compatible\"].modelId,\n      });\n    }\n\n    return providers;\n  }, [modelStatus, duckBrain.currentModel, providerConfigs]);\n\n  const handleSend = useCallback(\n    async (message: string) => {\n      await generateSQL(message);\n    },\n    [generateSQL]\n  );\n\n  const handleExecuteSQL = useCallback(\n    async (messageId: string, sql: string) => {\n      try {\n        const result = await executeQueryInChat(messageId, sql);\n        if (result) {\n          toast.success(`Query returned ${result.rowCount} rows`);\n        }\n      } catch {\n        // Error is already handled in the store and shown in ResultsArtifact\n      }\n    },\n    [executeQueryInChat]\n  );\n\n  const handleInsertSQL = useCallback(\n    (sql: string) => {\n      updateTabQuery(tabId, sql);\n      toast.success(\"SQL inserted into editor\");\n    },\n    [updateTabQuery, tabId]\n  );\n\n  const handleInitialize = useCallback(async () => {\n    await initializeDuckBrain(DEFAULT_MODEL.id);\n  }, [initializeDuckBrain]);\n\n  // Render WebGPU not supported state\n  if (isWebGPUSupported === false) {\n    return (\n      <div className=\"flex flex-col h-full border-l bg-background\">\n        <Header onClose={toggleBrainPanel} />\n        <div className=\"flex-1 flex items-center justify-center p-4\">\n          <Alert variant=\"destructive\" className=\"max-w-sm\">\n            <AlertCircle className=\"h-4 w-4\" />\n            <AlertDescription className=\"mt-2\">\n              <p className=\"font-medium\">WebGPU Not Supported</p>\n              <p className=\"text-xs mt-1\">\n                Duck Brain requires WebGPU for local AI processing. Please use Chrome 113+ or Edge\n                113+.\n              </p>\n            </AlertDescription>\n          </Alert>\n        </div>\n      </div>\n    );\n  }\n\n  // Check if external provider is configured\n  const hasExternalProvider =\n    (aiProvider === \"openai\" && providerConfigs.openai?.apiKey) ||\n    (aiProvider === \"anthropic\" && providerConfigs.anthropic?.apiKey) ||\n    (aiProvider === \"openai-compatible\" &&\n      providerConfigs[\"openai-compatible\"]?.baseUrl &&\n      providerConfigs[\"openai-compatible\"]?.modelId);\n\n  // Render idle state (not initialized) - only for WebLLM without external provider\n  if ((modelStatus === \"idle\" || modelStatus === \"checking\") && !hasExternalProvider) {\n    return (\n      <div className=\"flex flex-col h-full border-l bg-background\">\n        <Header onClose={toggleBrainPanel} />\n        <div className=\"flex-1 flex items-center justify-center p-4\">\n          <div className=\"text-center max-w-sm\">\n            <Brain className=\"h-12 w-12 mx-auto mb-4 text-primary\" />\n            <h3 className=\"font-semibold mb-2\">Initialize Duck Brain</h3>\n            <p className=\"text-sm text-muted-foreground mb-4\">\n              Download an AI model to enable natural language to SQL conversion. This runs 100%\n              locally in your browser.\n            </p>\n            <div className=\"space-y-2 text-xs text-muted-foreground mb-4\">\n              <p>\n                <strong>Model:</strong> {DEFAULT_MODEL.displayName}\n              </p>\n              <p>\n                <strong>Size:</strong> {DEFAULT_MODEL.size}\n              </p>\n              <p>First load downloads the model. Future loads use cache.</p>\n            </div>\n            <Button onClick={handleInitialize} className=\"gap-2\">\n              <Download className=\"h-4 w-4\" />\n              Load AI Model\n            </Button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  // Render downloading/loading state\n  if (modelStatus === \"downloading\" || modelStatus === \"loading\") {\n    return (\n      <div className=\"flex flex-col h-full border-l bg-background\">\n        <Header onClose={toggleBrainPanel} />\n        <div className=\"flex-1 flex items-center justify-center p-4\">\n          <div className=\"text-center max-w-sm w-full\">\n            <Loader2 className=\"h-8 w-8 mx-auto mb-4 animate-spin text-primary\" />\n            <h3 className=\"font-semibold mb-2\">\n              {modelStatus === \"downloading\" ? \"Downloading Model...\" : \"Loading Model...\"}\n            </h3>\n            <Progress value={downloadProgress} className=\"mb-2\" />\n            <p className=\"text-xs text-muted-foreground\">\n              {downloadStatus || `${downloadProgress}%`}\n            </p>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  // Render error state\n  if (modelStatus === \"error\") {\n    return (\n      <div className=\"flex flex-col h-full border-l bg-background\">\n        <Header onClose={toggleBrainPanel} />\n        <div className=\"flex-1 flex items-center justify-center p-4\">\n          <div className=\"text-center max-w-sm\">\n            <AlertCircle className=\"h-12 w-12 mx-auto mb-4 text-destructive\" />\n            <h3 className=\"font-semibold mb-2\">Failed to Load Model</h3>\n            <p className=\"text-sm text-muted-foreground mb-4\">\n              {error || \"An unknown error occurred\"}\n            </p>\n            <Button onClick={handleInitialize} variant=\"outline\" className=\"gap-2\">\n              <RefreshCw className=\"h-4 w-4\" />\n              Try Again\n            </Button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  // Render ready state with chat interface\n  return (\n    <div className=\"flex flex-col h-full border-l bg-background\">\n      <Header\n        onClose={toggleBrainPanel}\n        onClear={clearBrainMessages}\n        showClear={messages.length > 0}\n      />\n\n      {/* Status Badge & Provider Selector */}\n      <div className=\"px-3 py-2 border-b\">\n        <div className=\"flex items-center gap-2\">\n          <Badge variant=\"secondary\" className=\"bg-green-500/10 text-green-600 text-xs\">\n            <span className=\"w-1.5 h-1.5 rounded-full bg-green-500 mr-1.5\" />\n            Ready\n          </Badge>\n          {/* Provider selector - show when multiple providers available */}\n          {availableProviders.length > 1 ? (\n            <Select\n              value={aiProvider}\n              onValueChange={(value) => setAIProvider(value as AIProviderType)}\n            >\n              <SelectTrigger className=\"h-6 w-auto gap-1 px-2 text-xs border-0 bg-transparent\">\n                <div className=\"flex items-center gap-1\">\n                  {providerDisplayInfo.isCloud && <Cloud className=\"h-3 w-3\" />}\n                  <SelectValue />\n                </div>\n              </SelectTrigger>\n              <SelectContent>\n                {availableProviders.map((p) => (\n                  <SelectItem key={p.value} value={p.value} className=\"text-xs\">\n                    {p.label}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          ) : (\n            <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n              {providerDisplayInfo.isCloud && <Cloud className=\"h-3 w-3\" />}\n              <span>{providerDisplayInfo.name}</span>\n            </div>\n          )}\n        </div>\n      </div>\n\n      {/* Messages */}\n      <DuckBrainMessages\n        messages={messages}\n        streamingContent={streamingContent}\n        isGenerating={isGenerating}\n        onExecuteSQL={handleExecuteSQL}\n        onInsertSQL={handleInsertSQL}\n        className=\"flex-1\"\n      />\n\n      {/* Input */}\n      <div className=\"p-3 border-t\">\n        <DuckBrainInput\n          onSend={handleSend}\n          onAbort={abortGeneration}\n          isGenerating={isGenerating}\n          disabled={modelStatus !== \"ready\" && !hasExternalProvider}\n          databases={databases}\n          placeholder=\"Ask Duck Brain... (@ for tables)\"\n        />\n      </div>\n    </div>\n  );\n});\n\n// Header component\ninterface HeaderProps {\n  onClose: () => void;\n  onClear?: () => void;\n  showClear?: boolean;\n}\n\nconst Header: React.FC<HeaderProps> = ({ onClose, onClear, showClear }) => (\n  <div className=\"flex items-center justify-between px-3 py-2 border-b\">\n    <div className=\"flex items-center gap-2\">\n      <Brain className=\"h-5 w-5 text-primary\" />\n      <span className=\"font-semibold text-sm\">Duck Brain</span>\n    </div>\n    <div className=\"flex items-center gap-1\">\n      {showClear && onClear && (\n        <Button variant=\"ghost\" size=\"icon\" onClick={onClear} className=\"h-7 w-7\">\n          <Trash2 className=\"h-4 w-4\" />\n        </Button>\n      )}\n      <Button variant=\"ghost\" size=\"icon\" onClick={onClose} className=\"h-7 w-7\">\n        <X className=\"h-4 w-4\" />\n      </Button>\n    </div>\n  </div>\n);\n\nexport default DuckBrainPanel;\n"
  },
  {
    "path": "src/components/duck-brain/MarkdownContent.tsx",
    "content": "import React from \"react\";\nimport ReactMarkdown, { Components } from \"react-markdown\";\nimport { cn } from \"@/lib/utils\";\n\ninterface MarkdownContentProps {\n  content: string;\n  skipCodeBlocks?: boolean;\n  className?: string;\n}\n\n/**\n * Renders markdown content with custom styling.\n * When skipCodeBlocks is true, removes ```sql...``` blocks since they're handled separately.\n */\nconst MarkdownContent: React.FC<MarkdownContentProps> = ({\n  content,\n  skipCodeBlocks = false,\n  className,\n}) => {\n  // Remove ALL code blocks if they're handled separately by DuckBrainCodeBlock\n  // Uses two passes to catch all variations:\n  // 1. Fenced blocks with language identifier and newline: ```sql\\n...\\n```\n  // 2. Fallback for any remaining code blocks: ```...```\n  const processedContent = skipCodeBlocks\n    ? content\n        .replace(/```\\w*\\n[\\s\\S]*?```/g, \"\") // Code blocks with newline after lang\n        .replace(/```[\\s\\S]*?```/g, \"\") // Any remaining code blocks\n        .replace(/\\n{3,}/g, \"\\n\\n\") // Collapse multiple blank lines\n        .trim()\n    : content;\n\n  // If nothing left after removing code blocks, don't render\n  if (!processedContent) {\n    return null;\n  }\n\n  const components: Components = {\n    // Inline code styling\n    code: ({ className: codeClassName, children, ...props }) => {\n      // Check if this is a code block (has language class) vs inline code\n      const isCodeBlock = codeClassName?.includes(\"language-\");\n\n      if (isCodeBlock) {\n        return (\n          <pre className=\"bg-muted p-3 rounded-md overflow-x-auto my-2\">\n            <code className={cn(\"text-sm font-mono\", codeClassName)} {...props}>\n              {children}\n            </code>\n          </pre>\n        );\n      }\n\n      // Inline code\n      return (\n        <code className=\"bg-muted px-1.5 py-0.5 rounded text-sm font-mono\" {...props}>\n          {children}\n        </code>\n      );\n    },\n    // Paragraph styling\n    p: ({ children }) => <p className=\"mb-2 last:mb-0 leading-relaxed\">{children}</p>,\n    // Strong/bold\n    strong: ({ children }) => <strong className=\"font-semibold\">{children}</strong>,\n    // Emphasis/italic\n    em: ({ children }) => <em className=\"italic\">{children}</em>,\n    // Unordered list\n    ul: ({ children }) => <ul className=\"list-disc pl-4 mb-2 space-y-1\">{children}</ul>,\n    // Ordered list\n    ol: ({ children }) => <ol className=\"list-decimal pl-4 mb-2 space-y-1\">{children}</ol>,\n    // List item\n    li: ({ children }) => <li className=\"leading-relaxed\">{children}</li>,\n    // Links\n    a: ({ href, children }) => (\n      <a\n        href={href}\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className=\"text-primary underline hover:no-underline\"\n      >\n        {children}\n      </a>\n    ),\n    // Blockquote\n    blockquote: ({ children }) => (\n      <blockquote className=\"border-l-2 border-muted-foreground/30 pl-3 italic my-2\">\n        {children}\n      </blockquote>\n    ),\n    // Headings (rarely used in chat but good to have)\n    h1: ({ children }) => <h1 className=\"text-lg font-bold mt-3 mb-2\">{children}</h1>,\n    h2: ({ children }) => <h2 className=\"text-base font-bold mt-3 mb-2\">{children}</h2>,\n    h3: ({ children }) => <h3 className=\"text-sm font-bold mt-2 mb-1\">{children}</h3>,\n    // Horizontal rule\n    hr: () => <hr className=\"my-3 border-muted-foreground/20\" />,\n  };\n\n  return (\n    <div className={cn(\"text-sm prose-sm max-w-none\", className)}>\n      <ReactMarkdown components={components}>{processedContent}</ReactMarkdown>\n    </div>\n  );\n};\n\nexport default MarkdownContent;\n"
  },
  {
    "path": "src/components/duck-brain/ResultsArtifact.tsx",
    "content": "import React, { useState } from \"react\";\nimport { Loader2, AlertCircle, ChevronDown, ChevronUp, Table2, RotateCcw } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Collapsible, CollapsibleTrigger } from \"@/components/ui/collapsible\";\nimport { cn } from \"@/lib/utils\";\nimport type { QueryResultArtifact } from \"@/store\";\n\ninterface ResultsArtifactProps {\n  queryResult: QueryResultArtifact;\n  onRetry?: () => void;\n  className?: string;\n}\n\nconst MAX_INLINE_ROWS = 5;\nconst MAX_INLINE_COLUMNS = 6;\n\nconst ResultsArtifact: React.FC<ResultsArtifactProps> = ({ queryResult, onRetry, className }) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const { status, data, error } = queryResult;\n\n  // Running state\n  if (status === \"running\") {\n    return (\n      <div\n        className={cn(\n          \"flex items-center gap-2 p-3 rounded-lg border border-border bg-muted/30\",\n          className\n        )}\n      >\n        <Loader2 className=\"h-4 w-4 animate-spin text-primary\" />\n        <span className=\"text-sm text-muted-foreground\">Executing query...</span>\n      </div>\n    );\n  }\n\n  // Error state\n  if (status === \"error\") {\n    return (\n      <div\n        className={cn(\"p-3 rounded-lg border border-destructive/50 bg-destructive/5\", className)}\n      >\n        <div className=\"flex items-start gap-2\">\n          <AlertCircle className=\"h-4 w-4 text-destructive mt-0.5 flex-shrink-0\" />\n          <div className=\"flex-1 min-w-0\">\n            <p className=\"text-sm font-medium text-destructive\">Query failed</p>\n            <p className=\"text-xs text-muted-foreground mt-1 break-words\">\n              {error || \"Unknown error occurred\"}\n            </p>\n          </div>\n          {onRetry && (\n            <Button variant=\"ghost\" size=\"sm\" onClick={onRetry} className=\"flex-shrink-0 h-7 px-2\">\n              <RotateCcw className=\"h-3 w-3 mr-1\" />\n              Retry\n            </Button>\n          )}\n        </div>\n      </div>\n    );\n  }\n\n  // Pending state - shouldn't normally show, but handle it\n  if (status === \"pending\" || !data) {\n    return null;\n  }\n\n  // Success state - show results\n  const { columns, columnTypes, data: rows, rowCount } = data;\n  const hasMoreRows = rowCount > MAX_INLINE_ROWS;\n  const hasMoreColumns = columns.length > MAX_INLINE_COLUMNS;\n  const displayRows = isExpanded ? rows.slice(0, 50) : rows.slice(0, MAX_INLINE_ROWS);\n  const displayColumns = columns.slice(0, MAX_INLINE_COLUMNS);\n\n  return (\n    <div className={cn(\"rounded-lg border border-border overflow-hidden bg-card\", className)}>\n      {/* Header */}\n      <div className=\"flex items-center justify-between px-3 py-2 bg-muted/50 border-b\">\n        <div className=\"flex items-center gap-2\">\n          <Table2 className=\"h-3.5 w-3.5 text-muted-foreground\" />\n          <span className=\"text-xs font-medium\">Results</span>\n          <Badge variant=\"secondary\" className=\"text-[10px] px-1.5 py-0\">\n            {rowCount.toLocaleString()} row{rowCount !== 1 ? \"s\" : \"\"}\n          </Badge>\n        </div>\n      </div>\n\n      {/* Table */}\n      <div className=\"overflow-x-auto\">\n        <table className=\"w-full text-xs\">\n          <thead>\n            <tr className=\"border-b bg-muted/30\">\n              {displayColumns.map((col, i) => (\n                <th\n                  key={col}\n                  className=\"px-3 py-1.5 text-left font-medium text-muted-foreground whitespace-nowrap\"\n                >\n                  <div className=\"flex flex-col\">\n                    <span>{col}</span>\n                    <span className=\"text-[10px] font-normal opacity-60\">{columnTypes[i]}</span>\n                  </div>\n                </th>\n              ))}\n              {hasMoreColumns && (\n                <th className=\"px-3 py-1.5 text-left font-medium text-muted-foreground\">\n                  <span className=\"text-[10px]\">+{columns.length - MAX_INLINE_COLUMNS} more</span>\n                </th>\n              )}\n            </tr>\n          </thead>\n          <tbody>\n            {displayRows.map((row, rowIdx) => (\n              <tr key={rowIdx} className=\"border-b last:border-0 hover:bg-muted/20\">\n                {displayColumns.map((col) => (\n                  <td\n                    key={col}\n                    className=\"px-3 py-1.5 whitespace-nowrap max-w-[200px] truncate\"\n                    title={String(row[col] ?? \"\")}\n                  >\n                    {formatCellValue(row[col])}\n                  </td>\n                ))}\n                {hasMoreColumns && <td className=\"px-3 py-1.5 text-muted-foreground\">...</td>}\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n\n      {/* Expand/Collapse */}\n      {hasMoreRows && (\n        <Collapsible open={isExpanded} onOpenChange={setIsExpanded}>\n          <CollapsibleTrigger asChild>\n            <button className=\"w-full px-3 py-1.5 text-xs text-center text-muted-foreground hover:text-foreground hover:bg-muted/50 flex items-center justify-center gap-1 border-t\">\n              {isExpanded ? (\n                <>\n                  <ChevronUp className=\"h-3 w-3\" />\n                  Show less\n                </>\n              ) : (\n                <>\n                  <ChevronDown className=\"h-3 w-3\" />\n                  Show more ({Math.min(50, rowCount) - MAX_INLINE_ROWS} more rows)\n                </>\n              )}\n            </button>\n          </CollapsibleTrigger>\n        </Collapsible>\n      )}\n    </div>\n  );\n};\n\n// Helper to format cell values for display\nfunction formatCellValue(value: unknown): string {\n  if (value === null || value === undefined) {\n    return \"NULL\";\n  }\n  if (typeof value === \"boolean\") {\n    return value ? \"true\" : \"false\";\n  }\n  if (typeof value === \"object\") {\n    return JSON.stringify(value);\n  }\n  if (typeof value === \"number\") {\n    // Format numbers nicely\n    if (Number.isInteger(value)) {\n      return value.toLocaleString();\n    }\n    return value.toLocaleString(undefined, { maximumFractionDigits: 4 });\n  }\n  return String(value);\n}\n\nexport default ResultsArtifact;\n"
  },
  {
    "path": "src/components/duck-brain/SchemaAutocomplete.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport { Table2, Columns3 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport type { DatabaseInfo } from \"@/store\";\n\nexport interface SchemaSuggestion {\n  type: \"table\" | \"column\";\n  name: string;\n  fullPath: string;\n  tableName?: string;\n  columnType?: string;\n  rowCount?: number;\n}\n\ninterface SchemaAutocompleteProps {\n  isOpen: boolean;\n  suggestions: SchemaSuggestion[];\n  activeIndex: number;\n  onSelect: (suggestion: SchemaSuggestion) => void;\n  position: { top: number; left: number };\n  className?: string;\n}\n\n/**\n * Builds suggestions from database schema\n */\nexport function buildSchemaSuggestions(\n  databases: DatabaseInfo[],\n  filter: string = \"\"\n): SchemaSuggestion[] {\n  const suggestions: SchemaSuggestion[] = [];\n  const lowerFilter = filter.toLowerCase();\n\n  // Check if filter contains a dot (table.column)\n  const dotIndex = filter.indexOf(\".\");\n  const tableFilter = dotIndex > 0 ? filter.slice(0, dotIndex).toLowerCase() : null;\n  const columnFilter = dotIndex > 0 ? filter.slice(dotIndex + 1).toLowerCase() : null;\n\n  for (const db of databases) {\n    for (const table of db.tables) {\n      const tableName = db.name === \"memory\" ? table.name : `${db.name}.${table.name}`;\n\n      // If filtering for columns of a specific table\n      if (tableFilter) {\n        if (table.name.toLowerCase() === tableFilter || tableName.toLowerCase() === tableFilter) {\n          // Show columns for this table\n          for (const col of table.columns) {\n            if (!columnFilter || col.name.toLowerCase().startsWith(columnFilter)) {\n              suggestions.push({\n                type: \"column\",\n                name: col.name,\n                fullPath: `${table.name}.${col.name}`,\n                tableName: table.name,\n                columnType: col.type,\n              });\n            }\n          }\n        }\n      } else {\n        // Show tables matching filter\n        if (!lowerFilter || table.name.toLowerCase().startsWith(lowerFilter)) {\n          suggestions.push({\n            type: \"table\",\n            name: table.name,\n            fullPath: tableName,\n            rowCount: table.rowCount,\n          });\n        }\n      }\n    }\n  }\n\n  // Sort: tables first, then columns, alphabetically\n  return suggestions\n    .sort((a, b) => {\n      if (a.type !== b.type) return a.type === \"table\" ? -1 : 1;\n      return a.name.localeCompare(b.name);\n    })\n    .slice(0, 10); // Limit to 10 suggestions\n}\n\nconst SchemaAutocomplete: React.FC<SchemaAutocompleteProps> = ({\n  isOpen,\n  suggestions,\n  activeIndex,\n  onSelect,\n  position,\n  className,\n}) => {\n  const listRef = useRef<HTMLDivElement>(null);\n\n  // Scroll active item into view\n  useEffect(() => {\n    if (listRef.current && activeIndex >= 0) {\n      const activeItem = listRef.current.children[activeIndex] as HTMLElement;\n      activeItem?.scrollIntoView({ block: \"nearest\" });\n    }\n  }, [activeIndex]);\n\n  if (!isOpen || suggestions.length === 0) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        \"absolute z-50 w-64 max-h-48 overflow-auto\",\n        \"bg-popover border rounded-md shadow-lg\",\n        className\n      )}\n      style={{ bottom: position.top, left: position.left }}\n    >\n      <div ref={listRef} className=\"py-1\">\n        {suggestions.map((suggestion, index) => (\n          <button\n            key={`${suggestion.type}-${suggestion.fullPath}`}\n            type=\"button\"\n            onClick={() => onSelect(suggestion)}\n            className={cn(\n              \"w-full px-3 py-1.5 text-left text-sm flex items-center gap-2\",\n              \"hover:bg-accent\",\n              index === activeIndex && \"bg-accent\"\n            )}\n          >\n            {suggestion.type === \"table\" ? (\n              <Table2 className=\"h-3.5 w-3.5 text-primary flex-shrink-0\" />\n            ) : (\n              <Columns3 className=\"h-3.5 w-3.5 text-muted-foreground flex-shrink-0\" />\n            )}\n            <div className=\"flex-1 min-w-0\">\n              <span className=\"font-medium truncate block\">{suggestion.name}</span>\n              {suggestion.type === \"table\" && (\n                <span className=\"text-[10px] text-muted-foreground\">\n                  {suggestion.rowCount ? `${suggestion.rowCount.toLocaleString()} rows` : \"table\"}\n                </span>\n              )}\n              {suggestion.type === \"column\" && suggestion.columnType && (\n                <span className=\"text-[10px] text-muted-foreground\">{suggestion.columnType}</span>\n              )}\n            </div>\n          </button>\n        ))}\n      </div>\n      <div className=\"px-3 py-1.5 text-[10px] text-muted-foreground border-t bg-muted/30\">\n        <kbd className=\"px-1 rounded bg-muted\">↑↓</kbd> navigate\n        <span className=\"mx-1.5\">·</span>\n        <kbd className=\"px-1 rounded bg-muted\">Tab</kbd> select\n        <span className=\"mx-1.5\">·</span>\n        <kbd className=\"px-1 rounded bg-muted\">Esc</kbd> close\n      </div>\n    </div>\n  );\n};\n\nexport default SchemaAutocomplete;\n"
  },
  {
    "path": "src/components/editor/SqlEditor.tsx",
    "content": "import React, { useRef, useEffect, useState, useCallback } from \"react\";\nimport {\n  Play,\n  Loader2,\n  Lightbulb,\n  Command,\n  Edit,\n  Share2,\n  Brain,\n  Bookmark,\n  ListTree,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { useDuckStore } from \"@/store\";\nimport { useTheme } from \"../theme/theme-provider\";\nimport { cn } from \"@/lib/utils\";\nimport { createEditor, useMonacoConfig, type EditorInstance } from \"./monacoConfig\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { Input } from \"@/components/ui/input\";\nimport { toast } from \"sonner\";\nimport { Badge } from \"@/components/ui/badge\";\nimport FloatingActionButton from \"@/components/common/FloatingActionButton\";\nimport { copyQueryURL } from \"@/hooks/useQueryFromURL\";\nimport SaveQueryDialog from \"@/components/saved-queries/SaveQueryDialog\";\nimport { ExplainPlanViewer } from \"@/components/workspace/ExplainPlanViewer\";\n\ninterface SqlEditorProps {\n  tabId: string;\n  title: string;\n  className?: string;\n}\n\nconst SqlEditor: React.FC<SqlEditorProps> = ({ tabId, title, className }) => {\n  const editorRef = useRef<HTMLDivElement>(null);\n  const editorInstanceRef = useRef<EditorInstance | null>(null);\n  const { theme } = useTheme();\n  const tabs = useDuckStore((s) => s.tabs);\n  const executeQuery = useDuckStore((s) => s.executeQuery);\n  const isExecuting = useDuckStore((s) => !!s.executingTabs[tabId]);\n  const updateTabTitle = useDuckStore((s) => s.updateTabTitle);\n  const toggleBrainPanel = useDuckStore((s) => s.toggleBrainPanel);\n  const duckBrain = useDuckStore((s) => s.duckBrain);\n  const currentProfileId = useDuckStore((s) => s.currentProfileId);\n  const monacoConfig = useMonacoConfig(theme);\n\n  const currentTab = tabs.find((tab) => tab.id === tabId);\n  const currentContent =\n    currentTab?.type === \"sql\" && typeof currentTab.content === \"string\" ? currentTab.content : \"\";\n\n  const [isEditingTitle, setIsEditingTitle] = useState(false);\n  const [currentTitle, setCurrentTitle] = useState(title);\n  const [saveDialogOpen, setSaveDialogOpen] = useState(false);\n  const [explainOpen, setExplainOpen] = useState(false);\n  const [explainText, setExplainText] = useState(\"\");\n\n  // Stable callback for query execution\n  const stableExecuteCallback = useCallback(\n    async (query: string, queryTabId: string) => {\n      await executeQuery(query, queryTabId);\n    },\n    [executeQuery] // Add executeQuery as a dependency\n  );\n\n  // Editor initialization effect\n  useEffect(() => {\n    if (!editorRef.current) return;\n\n    // Initialize editor with stable configuration\n    editorInstanceRef.current = createEditor(\n      editorRef.current,\n      monacoConfig,\n      currentContent,\n      tabId,\n      stableExecuteCallback\n    );\n\n    // Cleanup function\n    return () => {\n      if (editorInstanceRef.current) {\n        editorInstanceRef.current.dispose();\n        editorInstanceRef.current = null;\n      }\n    };\n  }, [tabId, monacoConfig, stableExecuteCallback]); // Keep stableExecuteCallback\n\n  // Content sync effect\n  useEffect(() => {\n    const editor = editorInstanceRef.current?.editor;\n    if (editor && currentContent !== editor.getValue()) {\n      const position = editor.getPosition();\n      editor.setValue(currentContent);\n      if (position) {\n        editor.setPosition(position);\n      }\n    }\n  }, [currentContent]); // Only depend on currentContent\n\n  const handleExecuteQuery = async () => {\n    const editor = editorInstanceRef.current?.editor;\n    if (!editor || isExecuting) return;\n\n    const query = editor.getValue().trim();\n    if (!query) return;\n\n    try {\n      await executeQuery(query, tabId);\n    } catch (error) {\n      console.error(\"Query execution failed:\", error);\n      toast.error(\"Query execution failed\");\n    }\n  };\n\n  const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setCurrentTitle(e.target.value);\n  };\n\n  const handleTitleSubmit = () => {\n    if (currentTitle.trim()) {\n      updateTabTitle(tabId, currentTitle);\n      setIsEditingTitle(false);\n      toast.success(`Tab title updated to ${currentTitle}`);\n    } else {\n      setCurrentTitle(title);\n      setIsEditingTitle(false);\n      toast.error(\"Title cannot be empty\");\n    }\n  };\n\n  const handleTitleEdit = () => {\n    setIsEditingTitle(true);\n  };\n\n  const handleExplainQuery = async () => {\n    const editor = editorInstanceRef.current?.editor;\n    if (!editor || isExecuting) return;\n\n    const query = editor.getValue().trim();\n    if (!query) return;\n\n    try {\n      // Run without tabId so the result is returned without overwriting the tab's data\n      const result = await executeQuery(`EXPLAIN ANALYZE ${query}`);\n      if (result && result.data?.length > 0) {\n        // DuckDB returns rows with explain_key / explain_value — the analyzed_plan row has the full plan\n        const planRow = result.data.find((row) => row[\"explain_key\"] === \"analyzed_plan\");\n        const planText = planRow\n          ? String(planRow[\"explain_value\"])\n          : result.data.map((row) => String(row[\"explain_value\"] ?? \"\")).join(\"\\n\");\n        setExplainText(planText);\n        setExplainOpen(true);\n      }\n    } catch (error) {\n      console.error(\"Explain failed:\", error);\n      toast.error(\"Explain query failed\");\n    }\n  };\n\n  const handleShareQuery = async () => {\n    const editor = editorInstanceRef.current?.editor;\n    if (!editor) return;\n\n    const query = editor.getValue().trim();\n    if (!query) {\n      toast.error(\"No query to share\");\n      return;\n    }\n\n    const success = await copyQueryURL(query, false);\n    if (success) {\n      toast.success(\"Query URL copied to clipboard\");\n    } else {\n      toast.error(\"Failed to copy URL\");\n    }\n  };\n\n  return (\n    <div className={cn(\"flex flex-col h-full relative\", className)}>\n      {/* Header */}\n      <div className=\"flex items-center justify-between px-4 py-2 border-b\">\n        {/* Title (always visible) */}\n        <div className=\"flex items-center gap-2 flex-1 min-w-0\">\n          {isEditingTitle ? (\n            <Input\n              className=\"text-sm font-medium truncate max-w-[200px]\"\n              value={currentTitle}\n              onChange={handleTitleChange}\n              onBlur={handleTitleSubmit}\n              onKeyDown={(e) => {\n                if (e.key === \"Enter\") {\n                  handleTitleSubmit();\n                } else if (e.key === \"Escape\") {\n                  setCurrentTitle(title);\n                  setIsEditingTitle(false);\n                }\n              }}\n              autoFocus\n            />\n          ) : (\n            <div className=\"flex items-center gap-2\">\n              <span className=\"text-lg font-medium truncate text-sm\">{currentTitle}</span>\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                onClick={handleTitleEdit}\n                className=\"group-hover:opacity-100 transition-opacity hidden md:flex\"\n                aria-label=\"Edit tab title\"\n              >\n                <Edit className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          )}\n        </div>\n\n        {/* Desktop Actions */}\n        <div className=\"hidden md:flex items-center gap-4\">\n          <div className=\"flex gap-2 text-sm text-muted-foreground\">\n            <TooltipProvider>\n              <Tooltip delayDuration={200}>\n                <TooltipTrigger className=\"hover:bg-muted/50 p-2 rounded-md transition-colors\">\n                  <Lightbulb className=\"h-5 w-5 text-yellow-500/70 hover:text-yellow-500 transition-colors\" />\n                </TooltipTrigger>\n                <TooltipContent side=\"bottom\" className=\"w-72 p-0\" sideOffset={5}>\n                  <div className=\"bg-card px-3 py-2 rounded-t-sm border-b\">\n                    <h4 className=\"font-medium flex items-center gap-2\">\n                      <Command className=\"h-4 w-4\" />\n                      SQL Editor Shortcuts\n                    </h4>\n                  </div>\n                  <div className=\"p-3 space-y-3\">\n                    <div className=\"flex items-center justify-between\">\n                      <span className=\"text-sm\">Run Query</span>\n                      <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n                        Ctrl + Enter\n                      </Badge>\n                    </div>\n                    <div className=\"flex items-center justify-between\">\n                      <span className=\"text-sm\">Run Selected</span>\n                      <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n                        Ctrl + Shift + Enter\n                      </Badge>\n                    </div>\n                  </div>\n                </TooltipContent>\n              </Tooltip>\n            </TooltipProvider>\n          </div>\n          <TooltipProvider>\n            <Tooltip delayDuration={200}>\n              <TooltipTrigger asChild>\n                <Button onClick={handleShareQuery} variant=\"ghost\" size=\"icon\" className=\"h-9 w-9\">\n                  <Share2 className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"bottom\">\n                <p>Copy shareable URL</p>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n          <TooltipProvider>\n            <Tooltip delayDuration={200}>\n              <TooltipTrigger asChild>\n                <Button\n                  onClick={() => setSaveDialogOpen(true)}\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"h-9 w-9\"\n                  disabled={!currentContent.trim() || !currentProfileId}\n                >\n                  <Bookmark className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"bottom\">\n                <p>Save Query</p>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n          <TooltipProvider>\n            <Tooltip delayDuration={200}>\n              <TooltipTrigger asChild>\n                <Button\n                  onClick={toggleBrainPanel}\n                  variant={duckBrain.isPanelOpen ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"h-9 w-9\"\n                >\n                  <Brain className={cn(\"h-4 w-4\", duckBrain.isPanelOpen && \"text-primary\")} />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"bottom\">\n                <p>{duckBrain.isPanelOpen ? \"Close Duck Brain\" : \"Open Duck Brain\"}</p>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n          <TooltipProvider>\n            <Tooltip delayDuration={200}>\n              <TooltipTrigger asChild>\n                <Button\n                  onClick={handleExplainQuery}\n                  disabled={isExecuting}\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"h-9 w-9\"\n                >\n                  <ListTree className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"bottom\">\n                <p>Explain Plan</p>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n          <Button\n            onClick={handleExecuteQuery}\n            disabled={isExecuting}\n            variant=\"outline\"\n            className=\"flex items-center gap-2 min-w-[100px]\"\n          >\n            {isExecuting ? (\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n            ) : (\n              <Play className=\"h-4 w-4\" />\n            )}\n            {isExecuting ? \"Running...\" : \"Run Query\"}\n          </Button>\n        </div>\n      </div>\n\n      {/* Editor */}\n      <div className=\"flex-1 relative\">\n        <div ref={editorRef} className=\"h-full w-full absolute inset-0\" />\n      </div>\n\n      {/* Mobile FAB */}\n      <FloatingActionButton\n        onClick={handleExecuteQuery}\n        icon={isExecuting ? Loader2 : Play}\n        label={isExecuting ? \"Running...\" : \"Run\"}\n        disabled={isExecuting}\n        className={isExecuting ? \"animate-pulse\" : \"\"}\n      />\n\n      <SaveQueryDialog\n        open={saveDialogOpen}\n        onOpenChange={setSaveDialogOpen}\n        defaultName={currentTitle}\n        sqlText={currentContent}\n      />\n\n      <ExplainPlanViewer\n        open={explainOpen}\n        onOpenChange={setExplainOpen}\n        explainText={explainText}\n      />\n    </div>\n  );\n};\n\nexport default SqlEditor;\n"
  },
  {
    "path": "src/components/editor/monacoConfig.ts",
    "content": "// monacoConfig.ts\nimport * as monaco from \"monaco-editor\";\nimport { useDuckStore } from \"@/store\";\nimport { useMemo } from \"react\";\nimport type { editor } from \"monaco-editor\";\nimport { toast } from \"sonner\";\nimport editorWorker from \"monaco-editor/esm/vs/editor/editor.worker?worker\";\nimport { format } from \"sql-formatter\";\nimport { sqlEscapeString } from \"@/lib/sqlSanitize\";\n\n// Types\nexport interface EditorInstance {\n  editor: editor.IStandaloneCodeEditor;\n  dispose: () => void;\n}\n\ninterface EditorConfig {\n  language: string;\n  theme: string;\n  automaticLayout: boolean;\n  tabSize: number;\n  minimap: { enabled: boolean };\n  padding: { top: number };\n  suggestOnTriggerCharacters: boolean;\n  quickSuggestions: boolean;\n  wordBasedSuggestions: boolean;\n  fontSize: number;\n  lineNumbers: \"on\" | \"off\" | \"relative\";\n  scrollBeyondLastLine: boolean;\n  cursorBlinking: \"blink\" | \"smooth\" | \"phase\" | \"expand\" | \"solid\";\n  matchBrackets: \"always\" | \"never\" | \"near\";\n  rulers: number[];\n}\n\n// Worker configuration\nself.MonacoEnvironment = {\n  getWorker(_workerId: string) {\n    return new editorWorker();\n  },\n};\n\n// Create editor instance\nexport const createEditor = (\n  container: HTMLElement,\n  config: EditorConfig,\n  initialContent: string,\n  tabId: string,\n  executeQueryFn: (query: string, tabId: string) => Promise<void>\n): EditorInstance => {\n  const editor = monaco.editor.create(container, {\n    ...config,\n    value: initialContent,\n    wordBasedSuggestions: config.wordBasedSuggestions ? \"allDocuments\" : \"off\",\n    bracketPairColorization: { enabled: true },\n    guides: { bracketPairs: true, indentation: true },\n    renderWhitespace: \"selection\",\n    smoothScrolling: true,\n    cursorSmoothCaretAnimation: \"on\",\n    formatOnPaste: true,\n    formatOnType: true,\n    snippetSuggestions: \"inline\",\n    suggest: {\n      preview: true,\n      showMethods: true,\n      showFunctions: true,\n      showVariables: true,\n      showWords: true,\n      showColors: true,\n    },\n  });\n\n  // Add commands\n  editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, async () => {\n    const query = editor.getValue().trim();\n    if (!query) {\n      toast.error(\"Please enter a query to execute\");\n      return;\n    }\n    try {\n      await executeQueryFn(query, tabId);\n    } catch (err) {\n      toast.error(\n        `Query execution failed: ${err instanceof Error ? err.message : \"Unknown error\"}`\n      );\n    }\n  });\n\n  editor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.KeyF, () => {\n    const formatAction = editor.getAction(\"editor.action.formatDocument\");\n    formatAction?.run();\n  });\n\n  // Add context menu actions\n  editor.addAction({\n    id: \"execute-selection\",\n    label: \"Execute Selected Query\",\n    keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],\n    contextMenuGroupId: \"navigation\",\n    run: async (ed) => {\n      const selection = ed.getSelection();\n      const selectedText = selection ? ed.getModel()?.getValueInRange(selection) : \"\";\n\n      if (selectedText?.trim()) {\n        try {\n          await executeQueryFn(selectedText.trim(), tabId);\n        } catch (err) {\n          toast.error(\n            `Query execution failed: ${err instanceof Error ? err.message : \"Unknown error\"}`\n          );\n        }\n      }\n    },\n  });\n\n  editor.addAction({\n    id: \"format-sql\",\n    label: \"Format SQL\",\n    keybindings: [monaco.KeyMod.Alt | monaco.KeyCode.KeyF],\n    contextMenuGroupId: \"modification\",\n    run: (ed) => {\n      const text = ed.getValue();\n      try {\n        const formatted = format(text, {\n          language: \"sql\",\n          keywordCase: \"upper\",\n          indentStyle: \"standard\",\n          linesBetweenQueries: 2,\n        });\n        ed.setValue(formatted);\n      } catch {\n        toast.error(\"Failed to format SQL\");\n      }\n    },\n  });\n\n  // Setup content change listener with debounce\n  let timeoutId: number;\n  const disposable = editor.onDidChangeModelContent(() => {\n    clearTimeout(timeoutId);\n    timeoutId = window.setTimeout(() => {\n      const newValue = editor.getValue();\n      useDuckStore.getState().updateTabQuery(tabId, newValue);\n    }, 300);\n  });\n\n  return {\n    editor,\n    dispose: () => {\n      clearTimeout(timeoutId);\n      disposable.dispose();\n      editor.dispose();\n    },\n  };\n};\n\n// Create a lightweight editor for notebook cells (no auto-save to tab query)\nexport const createCellEditor = (\n  container: HTMLElement,\n  config: EditorConfig,\n  initialContent: string,\n  executeQueryFn: () => Promise<void>,\n  onContentChange: (value: string) => void\n): EditorInstance => {\n  const editor = monaco.editor.create(container, {\n    ...config,\n    value: initialContent,\n    wordBasedSuggestions: config.wordBasedSuggestions ? \"allDocuments\" : \"off\",\n    bracketPairColorization: { enabled: true },\n    guides: { bracketPairs: true, indentation: true },\n    renderWhitespace: \"selection\",\n    smoothScrolling: true,\n    cursorSmoothCaretAnimation: \"on\",\n    formatOnPaste: true,\n    formatOnType: true,\n    snippetSuggestions: \"inline\",\n    suggest: {\n      preview: true,\n      showMethods: true,\n      showFunctions: true,\n      showVariables: true,\n      showWords: true,\n      showColors: true,\n    },\n  });\n\n  // Ctrl/Cmd+Enter to run cell\n  editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, async () => {\n    const query = editor.getValue().trim();\n    if (!query) return;\n    try {\n      await executeQueryFn();\n    } catch (err) {\n      toast.error(\n        `Query execution failed: ${err instanceof Error ? err.message : \"Unknown error\"}`\n      );\n    }\n  });\n\n  // Shift+Enter to run cell (Jupyter-style)\n  editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, async () => {\n    const query = editor.getValue().trim();\n    if (!query) return;\n    try {\n      await executeQueryFn();\n    } catch (err) {\n      toast.error(\n        `Query execution failed: ${err instanceof Error ? err.message : \"Unknown error\"}`\n      );\n    }\n  });\n\n  editor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.KeyF, () => {\n    const formatAction = editor.getAction(\"editor.action.formatDocument\");\n    formatAction?.run();\n  });\n\n  editor.addAction({\n    id: \"format-sql\",\n    label: \"Format SQL\",\n    keybindings: [monaco.KeyMod.Alt | monaco.KeyCode.KeyF],\n    contextMenuGroupId: \"modification\",\n    run: (ed) => {\n      const text = ed.getValue();\n      try {\n        const formatted = format(text, {\n          language: \"sql\",\n          keywordCase: \"upper\",\n          indentStyle: \"standard\",\n          linesBetweenQueries: 2,\n        });\n        ed.setValue(formatted);\n      } catch {\n        toast.error(\"Failed to format SQL\");\n      }\n    },\n  });\n\n  // Content change listener — calls provided callback instead of updateTabQuery\n  let timeoutId: number;\n  const disposable = editor.onDidChangeModelContent(() => {\n    clearTimeout(timeoutId);\n    timeoutId = window.setTimeout(() => {\n      onContentChange(editor.getValue());\n    }, 300);\n  });\n\n  return {\n    editor,\n    dispose: () => {\n      clearTimeout(timeoutId);\n      disposable.dispose();\n      editor.dispose();\n    },\n  };\n};\n\n// Enhanced config hook with better defaults\nexport const useMonacoConfig = (theme: string): EditorConfig => {\n  return useMemo(\n    () => ({\n      language: \"sql\",\n      theme: theme === \"dark\" ? \"vs-dark\" : \"vs\",\n      automaticLayout: true,\n      tabSize: 2,\n      minimap: { enabled: false },\n      padding: { top: 10 },\n      suggestOnTriggerCharacters: true,\n      quickSuggestions: true,\n      wordBasedSuggestions: false,\n      fontSize: 12,\n      lineNumbers: \"on\",\n      scrollBeyondLastLine: false,\n      cursorBlinking: \"blink\",\n      matchBrackets: \"always\",\n      rulers: [],\n    }),\n    [theme]\n  );\n};\n\n// Register SQL formatting provider\nmonaco.languages.registerDocumentFormattingEditProvider(\"sql\", {\n  provideDocumentFormattingEdits: (model) => {\n    try {\n      const formatted = format(model.getValue(), {\n        language: \"sql\",\n        keywordCase: \"upper\",\n        indentStyle: \"standard\",\n        linesBetweenQueries: 2,\n      });\n\n      return [\n        {\n          range: model.getFullModelRange(),\n          text: formatted,\n        },\n      ];\n    } catch (err) {\n      console.error(\"SQL formatting failed:\", err);\n      return [];\n    }\n  },\n});\n\n// Adapt to use the WASM autocompletion\ninterface AutocompleteItem {\n  suggestion: string;\n}\nconst queryNative = async <T>(\n  connection: { query: (sql: string) => Promise<{ toArray: () => unknown[] }> },\n  query: string\n): Promise<T[]> => {\n  const results = await connection.query(query);\n  return results.toArray().map((row: unknown) => row as T);\n};\n\nmonaco.languages.registerCompletionItemProvider(\"sql\", {\n  triggerCharacters: [\" \", \".\", \"(\", \",\"],\n  async provideCompletionItems(model, position) {\n    const word = model.getWordUntilPosition(position);\n    const range = {\n      startLineNumber: position.lineNumber,\n      endLineNumber: position.lineNumber,\n      startColumn: word.startColumn,\n      endColumn: word.endColumn,\n    };\n    const textInRange = model.getValueInRange({\n      startColumn: 0,\n      endColumn: position.column,\n      startLineNumber: position.lineNumber,\n      endLineNumber: position.lineNumber,\n    });\n\n    // Get the connection and ensure it's valid\n    const { connection } = useDuckStore.getState();\n    if (!connection) {\n      console.warn(\"No database connection available for autocompletion.\");\n      return { suggestions: [] };\n    }\n    try {\n      const escapedText = sqlEscapeString(textInRange);\n      const query = `select suggestion from sql_auto_complete('${escapedText}')`;\n      const items: AutocompleteItem[] = await queryNative<AutocompleteItem>(connection, query);\n\n      const suggestions = items.map((item) => {\n        return {\n          label: String(item.suggestion),\n          kind: monaco.languages.CompletionItemKind.Field,\n          insertText: String(item.suggestion),\n          range,\n        };\n      });\n\n      return { suggestions };\n    } catch (error) {\n      console.error(\"Autocompletion query failed:\", error);\n      return { suggestions: [] };\n    }\n  },\n});\n\n// Export everything needed\nexport default {\n  createEditor,\n  useMonacoConfig,\n};\n"
  },
  {
    "path": "src/components/explorer/ColumnNode.tsx",
    "content": "import React, { useState } from \"react\";\nimport { ChevronRight, ChevronDown, Hash, Type, Calendar, ToggleLeft } from \"lucide-react\";\nimport { type ColumnStats } from \"@/store\";\n\ninterface ColumnNodeProps {\n  stats: ColumnStats;\n}\n\nconst getTypeIcon = (type: string) => {\n  const upperType = type.toUpperCase();\n  if (\n    upperType.includes(\"INT\") ||\n    upperType.includes(\"DOUBLE\") ||\n    upperType.includes(\"FLOAT\") ||\n    upperType.includes(\"DECIMAL\")\n  ) {\n    return <Hash className=\"w-3 h-3\" />;\n  } else if (upperType.includes(\"DATE\") || upperType.includes(\"TIME\")) {\n    return <Calendar className=\"w-3 h-3\" />;\n  } else if (upperType.includes(\"BOOL\")) {\n    return <ToggleLeft className=\"w-3 h-3\" />;\n  }\n  return <Type className=\"w-3 h-3\" />;\n};\n\nconst getTypeColor = (type: string) => {\n  const upperType = type.toUpperCase();\n  if (\n    upperType.includes(\"INT\") ||\n    upperType.includes(\"DOUBLE\") ||\n    upperType.includes(\"FLOAT\") ||\n    upperType.includes(\"DECIMAL\")\n  ) {\n    return \"text-purple-500 bg-purple-500/10\";\n  } else if (upperType.includes(\"DATE\") || upperType.includes(\"TIME\")) {\n    return \"text-green-500 bg-green-500/10\";\n  } else if (upperType.includes(\"BOOL\")) {\n    return \"text-yellow-500 bg-yellow-500/10\";\n  }\n  return \"text-blue-500 bg-blue-500/10\";\n};\n\nconst getFillColor = (percentage: number) => {\n  if (percentage >= 90) return \"bg-green-500\";\n  if (percentage >= 50) return \"bg-yellow-500\";\n  return \"bg-red-500\";\n};\n\nexport const ColumnNode: React.FC<ColumnNodeProps> = ({ stats }) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  // Safe parsing function that handles both string and number types\n  const parseValue = (value: string | number): number => {\n    if (typeof value === \"number\") return value;\n    if (typeof value === \"string\") {\n      // Remove quotes if present\n      const cleaned = value.replace(/\"/g, \"\");\n      return parseFloat(cleaned) || 0;\n    }\n    return 0;\n  };\n\n  const nullPercentage = parseValue(stats.null_percentage);\n  const fillPercentage = 100 - nullPercentage;\n  const uniqueCount = stats.approx_unique ? parseValue(stats.approx_unique) : 0;\n  const totalCount = parseValue(stats.count);\n\n  const isNumeric =\n    stats.column_type.toUpperCase().includes(\"INT\") ||\n    stats.column_type.toUpperCase().includes(\"DOUBLE\") ||\n    stats.column_type.toUpperCase().includes(\"FLOAT\") ||\n    stats.column_type.toUpperCase().includes(\"DECIMAL\");\n\n  return (\n    <div className=\"ml-8\">\n      <div\n        className=\"flex items-center py-1.5 px-2 hover:bg-secondary/50 rounded-md cursor-pointer group\"\n        onClick={() => setIsExpanded(!isExpanded)}\n      >\n        <div className=\"flex-1 flex items-center gap-2 min-w-0\">\n          {isExpanded ? (\n            <ChevronDown className=\"w-3 h-3 flex-shrink-0 text-muted-foreground\" />\n          ) : (\n            <ChevronRight className=\"w-3 h-3 flex-shrink-0 text-muted-foreground\" />\n          )}\n\n          <div className={`flex-shrink-0 p-1 rounded ${getTypeColor(stats.column_type)}`}>\n            {getTypeIcon(stats.column_type)}\n          </div>\n\n          <div className=\"flex-1 min-w-0\">\n            <div className=\"flex items-center gap-2\">\n              <span className=\"text-xs font-medium truncate\">{stats.column_name}</span>\n              <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-mono\">\n                {stats.column_type}\n              </span>\n            </div>\n\n            {!isExpanded && (\n              <div className=\"flex items-center gap-2 mt-1\">\n                <div className=\"flex-1 max-w-[120px]\">\n                  <div className=\"flex items-center gap-1\">\n                    <div className=\"flex-1 h-1.5 bg-muted rounded-full overflow-hidden\">\n                      <div\n                        className={`h-full ${getFillColor(fillPercentage)} transition-all`}\n                        style={{ width: `${fillPercentage}%` }}\n                      />\n                    </div>\n                    <span className=\"text-[9px] text-muted-foreground font-mono\">\n                      {fillPercentage.toFixed(0)}%\n                    </span>\n                  </div>\n                </div>\n                <span className=\"text-[10px] text-muted-foreground\">\n                  {uniqueCount.toLocaleString()} unique\n                </span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n\n      {isExpanded && (\n        <div className=\"ml-6 mt-1 mb-2 p-2 bg-muted/30 rounded-md space-y-2\">\n          {/* Fill Percentage */}\n          <div className=\"space-y-1\">\n            <div className=\"flex items-center justify-between text-[10px]\">\n              <span className=\"text-muted-foreground\">Data Fill</span>\n              <span className=\"font-medium\">{fillPercentage.toFixed(1)}%</span>\n            </div>\n            <div className=\"h-2 bg-background rounded-full overflow-hidden\">\n              <div\n                className={`h-full ${getFillColor(fillPercentage)} transition-all`}\n                style={{ width: `${fillPercentage}%` }}\n              />\n            </div>\n          </div>\n\n          {/* Basic Stats */}\n          <div className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-[10px]\">\n            <div className=\"flex justify-between\">\n              <span className=\"text-muted-foreground\">Total:</span>\n              <span className=\"font-mono\">{totalCount.toLocaleString()}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span className=\"text-muted-foreground\">Unique:</span>\n              <span className=\"font-mono\">{uniqueCount.toLocaleString()}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span className=\"text-muted-foreground\">Nulls:</span>\n              <span className=\"font-mono\">{((nullPercentage / 100) * totalCount).toFixed(0)}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span className=\"text-muted-foreground\">Cardinality:</span>\n              <span className=\"font-mono\">\n                {isNaN((uniqueCount / totalCount) * 100)\n                  ? \"0.0\"\n                  : ((uniqueCount / totalCount) * 100).toFixed(1)}\n                %\n              </span>\n            </div>\n          </div>\n\n          {/* Numeric Stats */}\n          {isNumeric && stats.avg && (\n            <>\n              <div className=\"border-t border-border/50 my-1\" />\n              <div className=\"space-y-1 text-[10px]\">\n                <div className=\"flex justify-between\">\n                  <span className=\"text-muted-foreground\">Min:</span>\n                  <span className=\"font-mono\">{parseValue(stats.min!).toLocaleString()}</span>\n                </div>\n                <div className=\"flex justify-between\">\n                  <span className=\"text-muted-foreground\">Max:</span>\n                  <span className=\"font-mono\">{parseValue(stats.max!).toLocaleString()}</span>\n                </div>\n                <div className=\"flex justify-between\">\n                  <span className=\"text-muted-foreground\">Avg:</span>\n                  <span className=\"font-mono\">\n                    {parseValue(stats.avg).toLocaleString(undefined, {\n                      maximumFractionDigits: 2,\n                    })}\n                  </span>\n                </div>\n                {stats.std && (\n                  <div className=\"flex justify-between\">\n                    <span className=\"text-muted-foreground\">Std Dev:</span>\n                    <span className=\"font-mono\">\n                      {parseValue(stats.std).toLocaleString(undefined, {\n                        maximumFractionDigits: 2,\n                      })}\n                    </span>\n                  </div>\n                )}\n              </div>\n\n              {stats.q25 && stats.q50 && stats.q75 && (\n                <>\n                  <div className=\"border-t border-border/50 my-1\" />\n                  <div className=\"space-y-1 text-[10px]\">\n                    <div className=\"text-[9px] text-muted-foreground font-medium mb-1\">\n                      Quartiles\n                    </div>\n                    <div className=\"flex justify-between\">\n                      <span className=\"text-muted-foreground\">Q1 (25%):</span>\n                      <span className=\"font-mono text-[9px]\">\n                        {parseValue(stats.q25).toLocaleString()}\n                      </span>\n                    </div>\n                    <div className=\"flex justify-between\">\n                      <span className=\"text-muted-foreground\">Q2 (50%):</span>\n                      <span className=\"font-mono text-[9px]\">\n                        {parseValue(stats.q50).toLocaleString()}\n                      </span>\n                    </div>\n                    <div className=\"flex justify-between\">\n                      <span className=\"text-muted-foreground\">Q3 (75%):</span>\n                      <span className=\"font-mono text-[9px]\">\n                        {parseValue(stats.q75).toLocaleString()}\n                      </span>\n                    </div>\n                  </div>\n                </>\n              )}\n            </>\n          )}\n\n          {/* String Stats */}\n          {!isNumeric && stats.min && stats.max && (\n            <>\n              <div className=\"border-t border-border/50 my-1\" />\n              <div className=\"space-y-1 text-[10px]\">\n                <div className=\"flex justify-between gap-2\">\n                  <span className=\"text-muted-foreground flex-shrink-0\">Min:</span>\n                  <span className=\"font-mono truncate text-right\" title={stats.min}>\n                    {stats.min}\n                  </span>\n                </div>\n                <div className=\"flex justify-between gap-2\">\n                  <span className=\"text-muted-foreground flex-shrink-0\">Max:</span>\n                  <span className=\"font-mono truncate text-right\" title={stats.max}>\n                    {stats.max}\n                  </span>\n                </div>\n              </div>\n            </>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default ColumnNode;\n"
  },
  {
    "path": "src/components/explorer/DataExplorer.tsx",
    "content": "import React, { useState, useCallback, lazy, Suspense } from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Button } from \"@/components/ui/button\";\nimport { Database, EllipsisVertical, FileUp, Plus, RefreshCw, Server } from \"lucide-react\";\n\nconst FileImporter = lazy(() => import(\"./FileImporter\"));\nimport TreeNode, { TreeNodeData } from \"./TreeNode\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport FolderBrowser from \"@/components/folders/FolderBrowser\";\nimport CloudBrowser from \"@/components/cloud/CloudBrowser\";\nimport { type FileEntry, fileSystemService } from \"@/lib/fileSystem\";\nimport { type ImportOptions } from \"@/components/common/ImportOptionsPopover\";\nimport { toast } from \"sonner\";\n\nexport default function DataExplorer() {\n  const [isSheetOpen, setIsSheetOpen] = useState(false);\n  const [isDraggingOver, setIsDraggingOver] = useState(false);\n  const databases = useDuckStore((s) => s.databases);\n  const isLoading = useDuckStore((s) => s.isLoading);\n  const currentConnection = useDuckStore((s) => s.currentConnection);\n  const importFile = useDuckStore((s) => s.importFile);\n  const fetchDatabasesAndTablesInfo = useDuckStore((s) => s.fetchDatabasesAndTablesInfo);\n  const isFileSystemSupported = useDuckStore((s) => s.isFileSystemSupported);\n  const schemaFetchError = useDuckStore((s) => s.schemaFetchError);\n  const isLoadingDbTablesFetch = useDuckStore((s) => s.isLoadingDbTablesFetch);\n  const [searchTerm, setSearchTerm] = useState(\"\");\n\n  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setSearchTerm(e.target.value);\n  };\n\n  // Handle file import from mounted folder\n  const handleFolderFileImport = useCallback(\n    async (folderId: string, file: FileEntry, options: ImportOptions) => {\n      const { tableName, importMode } = options;\n      const modeLabel = importMode === \"view\" ? \"Linking\" : \"Importing\";\n      const resultLabel = importMode === \"view\" ? \"view\" : \"table\";\n\n      try {\n        toast.loading(`${modeLabel} ${file.name}...`, { id: \"folder-import\" });\n\n        // Read file from folder\n        const fileData = await fileSystemService.readFile(folderId, file.path);\n        const buffer = await fileData.arrayBuffer();\n\n        // Determine file type from extension\n        const ext = file.extension.replace(\".\", \"\").toLowerCase();\n        let fileType = ext;\n        if (ext === \"jsonl\" || ext === \"ndjson\") fileType = \"json\";\n\n        await importFile(file.name, buffer, tableName, fileType, undefined, { importMode });\n        await fetchDatabasesAndTablesInfo();\n\n        toast.success(`Created ${resultLabel} \"${tableName}\" from \"${file.name}\"`, {\n          id: \"folder-import\",\n        });\n      } catch (error) {\n        console.error(\"Failed to import file:\", error);\n        toast.error(\n          `Failed to import: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n          { id: \"folder-import\" }\n        );\n      }\n    },\n    [importFile, fetchDatabasesAndTablesInfo]\n  );\n  const buildTreeData = () => {\n    const treeData: TreeNodeData[] = databases.map((db) => ({\n      name: db.name,\n      type: \"database\",\n      children: db.tables.map((table) => ({\n        name: table.name,\n        type: \"table\",\n      })),\n    }));\n    return treeData;\n  };\n  const treeData = buildTreeData();\n  const isExternal = currentConnection?.scope === \"External\";\n\n  const handleDragOver = useCallback(\n    (e: React.DragEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n      if (e.dataTransfer.types.includes(\"Files\") && !isExternal) {\n        setIsDraggingOver(true);\n      }\n    },\n    [isExternal]\n  );\n\n  const handleDragLeave = useCallback((e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    // Only set false if we're leaving the container (not entering a child)\n    if (e.currentTarget === e.target || !e.currentTarget.contains(e.relatedTarget as Node)) {\n      setIsDraggingOver(false);\n    }\n  }, []);\n\n  const handleDrop = useCallback(\n    (e: React.DragEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n      setIsDraggingOver(false);\n      if (isExternal || !e.dataTransfer.files.length) return;\n      setIsSheetOpen(true);\n    },\n    [isExternal]\n  );\n\n  return (\n    <Card\n      className=\"h-full overflow-hidden border-none relative\"\n      onDragOver={handleDragOver}\n      onDragLeave={handleDragLeave}\n      onDrop={handleDrop}\n    >\n      {isDraggingOver && (\n        <div className=\"absolute inset-0 z-50 flex items-center justify-center bg-primary/10 border-2 border-dashed border-primary rounded-lg pointer-events-none\">\n          <div className=\"text-center\">\n            <FileUp className=\"h-10 w-10 text-primary mx-auto mb-2\" />\n            <p className=\"text-sm font-medium text-primary\">Drop files to import</p>\n          </div>\n        </div>\n      )}\n      {isLoading && (\n        <div className=\"flex items-center justify-center h-full\">\n          <p className=\"text-muted-foreground\">Loading databases...</p>\n        </div>\n      )}\n\n      <CardHeader className=\"p-2 border-b\">\n        <div className=\"flex items-center justify-between\">\n          <div className=\"flex items-center gap-2\">\n            {isExternal ? (\n              <Server className=\"h-4 w-4 text-primary\" />\n            ) : (\n              <Database className=\"h-4 w-4 text-primary\" />\n            )}\n            <CardTitle className=\"text-lg font-semibold\">Explorer</CardTitle>\n          </div>\n\n          <div className=\"flex items-center gap-1\">\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"h-8 w-8\"\n              onClick={() => fetchDatabasesAndTablesInfo()}\n              disabled={isLoadingDbTablesFetch}\n              title=\"Refresh Schema\"\n            >\n              <RefreshCw className={`h-4 w-4 ${isLoadingDbTablesFetch ? \"animate-spin\" : \"\"}`} />\n            </Button>\n\n            {/* Import menu - only show for non-external connections */}\n            {!isExternal && (\n              <DropdownMenu>\n                <DropdownMenuTrigger className=\"cursor-pointer p-2 border hover:bg-secondary rounded-md focus:outline-none\">\n                  <EllipsisVertical className=\"h-5 w-5\" />\n                </DropdownMenuTrigger>\n                <DropdownMenuPortal>\n                  <DropdownMenuContent>\n                    <DropdownMenuGroup>\n                      <DropdownMenuItem onClick={() => setIsSheetOpen(true)}>\n                        <FileUp className=\"h-4 w-4\" />\n                        Import Data\n                      </DropdownMenuItem>\n                    </DropdownMenuGroup>\n                  </DropdownMenuContent>\n                </DropdownMenuPortal>\n              </DropdownMenu>\n            )}\n          </div>\n\n          <Suspense fallback={null}>\n            <FileImporter\n              isSheetOpen={isSheetOpen}\n              setIsSheetOpen={setIsSheetOpen}\n              context={\"notEmpty\"}\n            />\n          </Suspense>\n        </div>\n      </CardHeader>\n\n      <CardContent className=\"p-2 h-[calc(100%-60px)] overflow-y-auto\">\n        <div className=\"space-y-4\">\n          {/* Databases Section */}\n          {databases.length > 0 ? (\n            <div className=\"space-y-2\">\n              <Input\n                type=\"text\"\n                placeholder=\"Search...\"\n                value={searchTerm}\n                onChange={handleSearch}\n                className=\"m-auto w-[calc(100%-2rem)] focus:ring-0\"\n              />\n              <div className=\"flex items-center justify-between px-2 mt-2\">\n                <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n                  Databases\n                </span>\n              </div>\n              <ul className=\"ml-2\" role=\"tree\" aria-label=\"Database schema\">\n                {treeData.map((node, index) => (\n                  <TreeNode\n                    key={index}\n                    node={node}\n                    level={0}\n                    searchTerm={searchTerm}\n                    refreshData={() => {}}\n                  />\n                ))}\n              </ul>\n            </div>\n          ) : (\n            <div className=\"flex flex-col items-center justify-center py-8 gap-4 text-center\">\n              <div className=\"flex flex-col items-center gap-2\">\n                {isExternal ? (\n                  <>\n                    <Server className=\"h-8 w-8 text-muted-foreground\" />\n                    {schemaFetchError ? (\n                      <>\n                        <p className=\"text-destructive text-sm\">Connection error</p>\n                        <p className=\"text-xs text-muted-foreground max-w-[200px]\">\n                          {schemaFetchError}\n                        </p>\n                      </>\n                    ) : (\n                      <>\n                        <p className=\"text-muted-foreground text-sm\">\n                          No schema found from the remote server.\n                        </p>\n                        <p className=\"text-xs text-muted-foreground\">\n                          Try refreshing or run queries directly in the editor.\n                        </p>\n                      </>\n                    )}\n                  </>\n                ) : (\n                  <>\n                    <Database className=\"h-8 w-8 text-muted-foreground\" />\n                    <p className=\"text-muted-foreground text-sm\">\n                      No databases found. Start by importing some data!\n                    </p>\n                  </>\n                )}\n              </div>\n              {isExternal ? (\n                <Button\n                  variant=\"outline\"\n                  className=\"gap-2\"\n                  onClick={() => fetchDatabasesAndTablesInfo()}\n                >\n                  <RefreshCw className=\"h-4 w-4\" />\n                  Refresh Schema\n                </Button>\n              ) : (\n                <Button variant=\"outline\" className=\"gap-2\" onClick={() => setIsSheetOpen(true)}>\n                  <Plus className=\"h-4 w-4\" />\n                  Import Data\n                </Button>\n              )}\n            </div>\n          )}\n\n          {/* Folder Browser Section - only show if supported and not external */}\n          {isFileSystemSupported && !isExternal && (\n            <div className=\"border-t pt-3\">\n              <FolderBrowser onFileImport={handleFolderFileImport} />\n            </div>\n          )}\n\n          {/* Cloud Storage Section */}\n          <div className=\"border-t pt-3\">\n            <CloudBrowser />\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "src/components/explorer/FileImporter.tsx",
    "content": "import React, { useCallback, useState, useMemo, useRef, useEffect } from \"react\";\nimport { format } from \"date-fns\";\nimport {\n  Upload,\n  FileWarning,\n  FileCheck,\n  Loader2,\n  X,\n  FileIcon,\n  Calendar,\n  HardDrive,\n  AlertTriangle,\n  RefreshCw,\n  Link as LinkIcon,\n  Link2,\n  Code,\n  ArrowLeft,\n  Check,\n  ChevronDown,\n  ChevronUp,\n  Eye,\n  Table,\n} from \"lucide-react\";\nimport { useDuckStore, type QueryResult } from \"@/store\";\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Button } from \"@/components/ui/button\";\nimport DuckUITable from \"@/components/table/DuckUItable\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"@/components/ui/sheet\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn, generateUUID } from \"@/lib/utils\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { z } from \"zod\";\nimport { toast } from \"sonner\";\n\n// Constants\nconst ACCEPTED_FILE_TYPES = {\n  \"text/csv\": [\".csv\"],\n  \"application/json\": [\".json\"],\n  \"application/octet-stream\": [\".parquet\", \".arrow\", \".db\", \".ddb\"],\n  \"application/vnd.duckdb\": [\".duckdb\", \".db\", \".ddb\"],\n  \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": [\".xlsx\"],\n} as const;\n\nconst MAX_FILE_SIZE = 3 * 1024 * 1024 * 1024; // 3GB\nconst SUPPORTED_FILE_EXTENSIONS = [\n  \"csv\",\n  \"json\",\n  \"parquet\",\n  \"arrow\",\n  \"duckdb\",\n  \"db\",\n  \"ddb\",\n  \"xlsx\",\n] as const;\nconst MAX_CONCURRENT_UPLOADS = 3;\nconst PREVIEW_ROW_LIMIT = 20;\n\n// DuckDB types for schema customization\nconst DUCKDB_TYPES = [\n  \"VARCHAR\",\n  \"INTEGER\",\n  \"BIGINT\",\n  \"DOUBLE\",\n  \"DECIMAL\",\n  \"DATE\",\n  \"TIMESTAMP\",\n  \"BOOLEAN\",\n  \"JSON\",\n  \"BLOB\",\n] as const;\n\n// Types\ntype FileExtension = (typeof SUPPORTED_FILE_EXTENSIONS)[number];\n\ninterface FileWithPreview extends File {\n  preview?: string;\n}\n\ninterface UploadError {\n  id: string;\n  message: string;\n  file?: string;\n  severity: \"error\" | \"warning\";\n}\n\ninterface FileImportState {\n  fileName: string;\n  status: \"pending\" | \"uploading\" | \"processing\" | \"success\" | \"error\";\n  progress?: number;\n  error?: string;\n}\n\n// CSV import options\ninterface CsvImportOptions {\n  ignoreErrors: boolean;\n  nullPadding: boolean;\n  allVarchar: boolean;\n  header: boolean;\n  delimiter: string;\n  autoDetect: boolean;\n  // Advanced options\n  quote?: string;\n  escape?: string;\n  skip?: number;\n  nullStr?: string;\n  dateFormat?: string;\n  timestampFormat?: string;\n  sampleSize?: number;\n}\n\n// Schema customization\ninterface SchemaColumn {\n  originalName: string;\n  newName: string;\n  type: string;\n  included: boolean;\n}\n\ninterface FileImporterProps {\n  isSheetOpen: boolean;\n  setIsSheetOpen: (open: boolean) => void;\n  context?: string;\n}\n\ninterface FileDetailsProps {\n  file: FileWithPreview;\n  tableName: string;\n  onTableNameChange: (name: string) => void;\n  status: FileImportState;\n  onRemove: () => void;\n  onRetry: () => void;\n  csvOptions?: CsvImportOptions;\n  onCsvOptionsChange?: (options: CsvImportOptions) => void;\n}\n\n// Utility Functions\nconst formatFileSize = (bytes: number): string => {\n  if (bytes === 0) return \"0 Bytes\";\n  const k = 1024;\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n};\n\nconst getErrorSuggestion = (errorMessage: string): string | null => {\n  const lowerError = errorMessage.toLowerCase();\n\n  // Network/URL errors\n  if (\n    lowerError.includes(\"fetch\") ||\n    lowerError.includes(\"network\") ||\n    lowerError.includes(\"cors\")\n  ) {\n    return \"Check your internet connection and ensure the URL is publicly accessible. CORS restrictions may prevent access to some URLs.\";\n  }\n\n  // File format errors\n  if (\n    lowerError.includes(\"invalid\") &&\n    (lowerError.includes(\"csv\") || lowerError.includes(\"json\") || lowerError.includes(\"parquet\"))\n  ) {\n    return \"The file format may be corrupted or not match the expected structure. Try opening the file locally to verify its contents.\";\n  }\n\n  // Parsing errors\n  if (lowerError.includes(\"parse\") || lowerError.includes(\"syntax\")) {\n    return \"Data parsing failed. Consider adjusting CSV options like delimiter, quote character, or enabling 'ignore errors'.\";\n  }\n\n  // Type detection errors\n  if (lowerError.includes(\"type\") || lowerError.includes(\"column\")) {\n    return \"Column type detection failed. Try enabling 'auto-detect types' or manually specify column types in schema customization.\";\n  }\n\n  // Authentication errors\n  if (\n    lowerError.includes(\"401\") ||\n    lowerError.includes(\"403\") ||\n    lowerError.includes(\"unauthorized\")\n  ) {\n    return \"Access denied. The URL may require authentication or the resource is not publicly accessible.\";\n  }\n\n  // Not found errors\n  if (lowerError.includes(\"404\") || lowerError.includes(\"not found\")) {\n    return \"The file was not found at the specified URL. Verify the URL is correct and the file still exists.\";\n  }\n\n  // Memory/size errors\n  if (lowerError.includes(\"memory\") || lowerError.includes(\"out of\")) {\n    return \"The file may be too large to process. Try importing a smaller file or use sampling options.\";\n  }\n\n  // Table name errors\n  if (lowerError.includes(\"table\") && lowerError.includes(\"exist\")) {\n    return \"A table with this name already exists. Choose a different name or the existing table will be replaced.\";\n  }\n\n  return null;\n};\n\nconst getFileIcon = (fileType: string) => {\n  const iconProps = { className: \"w-8 h-8\" };\n  switch (fileType.toLowerCase()) {\n    case \"csv\":\n      return <FileIcon {...iconProps} color=\"#38A169\" />;\n    case \"json\":\n      return <FileIcon {...iconProps} color=\"#D69E2E\" />;\n    case \"parquet\":\n      return <FileIcon {...iconProps} color=\"#3182CE\" />;\n    case \"arrow\":\n      return <FileIcon {...iconProps} color=\"#805AD5\" />;\n    case \"duckdb\":\n      return <FileIcon {...iconProps} color=\"#ED8936\" />;\n    case \"xlsx\":\n      return <FileIcon {...iconProps} color=\"#4299E1\" />;\n    default:\n      return <FileIcon {...iconProps} color=\"#718096\" />;\n  }\n};\n\n// Zod Schema for Table Name Validation\nconst tableNameSchema = z\n  .string()\n  .trim()\n  .min(1, \"Table name cannot be empty\")\n  .regex(/^[a-zA-Z0-9_]+$/, \"Table name can only contain letters, numbers, and underscores\");\n\nconst FileDetails: React.FC<FileDetailsProps> = ({\n  file,\n  tableName,\n  onTableNameChange,\n  status,\n  onRemove,\n  onRetry,\n  csvOptions,\n  onCsvOptionsChange,\n}) => {\n  const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);\n  const fileType = file.name.split(\".\").pop()?.toLowerCase() || \"\";\n  const lastModified = new Date(file.lastModified);\n  const isCsvFile = fileType === \"csv\";\n\n  const tableNameError = useMemo(() => {\n    try {\n      tableNameSchema.parse(tableName);\n      return null;\n    } catch (error) {\n      return error instanceof z.ZodError ? error.issues[0].message : \"Invalid table name\";\n    }\n  }, [tableName]);\n\n  // Handle CSV option changes\n  const handleCsvOptionChange = (\n    key: keyof CsvImportOptions,\n    value: string | boolean | number | undefined\n  ) => {\n    if (onCsvOptionsChange && csvOptions) {\n      onCsvOptionsChange({\n        ...csvOptions,\n        [key]: value,\n      });\n    }\n  };\n\n  return (\n    <div className=\"rounded-lg border p-4 shadow-sm\">\n      <div className=\"flex items-start gap-4\">\n        <div className=\"flex-shrink-0\">{getFileIcon(fileType)}</div>\n\n        <div className=\"flex-grow space-y-3\">\n          <div className=\"flex items-start justify-between\">\n            <div>\n              <h3 className=\"font-medium text-lg\">{file.name}</h3>\n              <div className=\"flex items-center gap-4 text-sm text-gray-600 mt-1\">\n                <TooltipProvider>\n                  <Tooltip>\n                    <TooltipTrigger className=\"flex items-center gap-1\">\n                      <HardDrive className=\"w-4 h-4\" />\n                      {formatFileSize(file.size)}\n                    </TooltipTrigger>\n                    <TooltipContent>File size</TooltipContent>\n                  </Tooltip>\n                </TooltipProvider>\n\n                <TooltipProvider>\n                  <Tooltip>\n                    <TooltipTrigger className=\"flex items-center gap-1\">\n                      <Calendar className=\"w-4 h-4\" />\n                      {format(lastModified, \"MMM dd, yyyy\")}\n                    </TooltipTrigger>\n                    <TooltipContent>Last modified</TooltipContent>\n                  </Tooltip>\n                </TooltipProvider>\n\n                <span className=\"uppercase px-2 py-0.5 rounded text-xs\">{fileType}</span>\n              </div>\n            </div>\n            <div className=\"flex items-center gap-2\">\n              {status.status === \"error\" && (\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={onRetry}\n                  className=\"text-gray-500 hover:text-gray-700\"\n                >\n                  <RefreshCw className=\"w-4 h-4\" />\n                </Button>\n              )}\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                onClick={onRemove}\n                className=\"text-gray-500 hover:text-gray-700\"\n              >\n                <X className=\"w-4 h-4\" />\n              </Button>\n            </div>\n          </div>\n\n          <div className=\"space-y-2\">\n            <Label htmlFor={`table-${file.name}`}>Table Name</Label>\n            <Input\n              id={`table-${file.name}`}\n              value={tableName}\n              required\n              onChange={(e) => onTableNameChange(e.target.value)}\n              placeholder=\"Enter table name\"\n              className=\"max-w-md p-2 ml-1\"\n              disabled={status.status === \"uploading\" || status.status === \"processing\"}\n            />\n            {tableNameError && <p className=\"text-sm text-red-500\">{tableNameError}</p>}\n            <p className=\"text-sm text-gray-500\">\n              This name will be used to reference the table in SQL queries\n            </p>\n          </div>\n\n          {/* CSV import options */}\n          {isCsvFile && csvOptions && (\n            <div className=\"mt-4 space-y-3\">\n              <div className=\"flex justify-between items-center\">\n                <h4 className=\"font-medium text-sm\">CSV Import Options</h4>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}\n                >\n                  {showAdvancedOptions ? \"Hide Options\" : \"Show Options\"}\n                </Button>\n              </div>\n\n              {showAdvancedOptions && (\n                <div className=\"p-3 rounded-md space-y-3\">\n                  <div className=\"grid grid-cols-2 gap-2\">\n                    <div className=\"flex items-center space-x-2\">\n                      <input\n                        type=\"checkbox\"\n                        id={`header-${file.name}`}\n                        checked={csvOptions.header}\n                        onChange={(e) => handleCsvOptionChange(\"header\", e.target.checked)}\n                        disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        className=\"h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n                      />\n                      <Label htmlFor={`header-${file.name}`} className=\"text-sm\">\n                        Has header row\n                      </Label>\n                    </div>\n\n                    <div className=\"flex items-center space-x-2\">\n                      <input\n                        type=\"checkbox\"\n                        id={`auto-detect-${file.name}`}\n                        checked={csvOptions.autoDetect}\n                        onChange={(e) => handleCsvOptionChange(\"autoDetect\", e.target.checked)}\n                        disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        className=\"h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n                      />\n                      <Label htmlFor={`auto-detect-${file.name}`} className=\"text-sm\">\n                        Auto-detect types\n                      </Label>\n                    </div>\n\n                    <div className=\"flex items-center space-x-2\">\n                      <input\n                        type=\"checkbox\"\n                        id={`ignore-errors-${file.name}`}\n                        checked={csvOptions.ignoreErrors}\n                        onChange={(e) => handleCsvOptionChange(\"ignoreErrors\", e.target.checked)}\n                        disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        className=\"h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n                      />\n                      <Label htmlFor={`ignore-errors-${file.name}`} className=\"text-sm\">\n                        Ignore errors\n                      </Label>\n                    </div>\n\n                    <div className=\"flex items-center space-x-2\">\n                      <input\n                        type=\"checkbox\"\n                        id={`null-padding-${file.name}`}\n                        checked={csvOptions.nullPadding}\n                        onChange={(e) => handleCsvOptionChange(\"nullPadding\", e.target.checked)}\n                        disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        className=\"h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n                      />\n                      <Label htmlFor={`null-padding-${file.name}`} className=\"text-sm\">\n                        Pad missing columns\n                      </Label>\n                    </div>\n                  </div>\n\n                  <div className=\"space-y-1\">\n                    <Label htmlFor={`delimiter-${file.name}`} className=\"text-sm\">\n                      Delimiter\n                    </Label>\n                    <div className=\"max-w-xs\">\n                      <Input\n                        id={`delimiter-${file.name}`}\n                        value={csvOptions.delimiter}\n                        onChange={(e) => handleCsvOptionChange(\"delimiter\", e.target.value)}\n                        placeholder=\"Delimiter character\"\n                        className=\"h-8\"\n                        disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                      />\n                    </div>\n                    <p className=\"text-xs text-gray-500\">\n                      Common values: , (comma), ; (semicolon), tab, pipe (|)\n                    </p>\n                  </div>\n\n                  {/* Advanced CSV Options */}\n                  <div className=\"space-y-3 pt-3 border-t\">\n                    <h5 className=\"font-medium text-sm text-muted-foreground\">Advanced Options</h5>\n\n                    <div className=\"grid grid-cols-2 gap-3\">\n                      <div className=\"space-y-1\">\n                        <Label htmlFor={`quote-${file.name}`} className=\"text-xs\">\n                          Quote Character\n                        </Label>\n                        <Input\n                          id={`quote-${file.name}`}\n                          value={csvOptions.quote || \"\"}\n                          onChange={(e) => handleCsvOptionChange(\"quote\", e.target.value)}\n                          placeholder={`\" (default)`}\n                          className=\"h-8 text-xs\"\n                          disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        />\n                      </div>\n\n                      <div className=\"space-y-1\">\n                        <Label htmlFor={`escape-${file.name}`} className=\"text-xs\">\n                          Escape Character\n                        </Label>\n                        <Input\n                          id={`escape-${file.name}`}\n                          value={csvOptions.escape || \"\"}\n                          onChange={(e) => handleCsvOptionChange(\"escape\", e.target.value)}\n                          placeholder={`\" (default)`}\n                          className=\"h-8 text-xs\"\n                          disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        />\n                      </div>\n\n                      <div className=\"space-y-1\">\n                        <Label htmlFor={`skip-${file.name}`} className=\"text-xs\">\n                          Skip Rows\n                        </Label>\n                        <Input\n                          id={`skip-${file.name}`}\n                          type=\"number\"\n                          min=\"0\"\n                          value={csvOptions.skip || \"\"}\n                          onChange={(e) =>\n                            handleCsvOptionChange(\"skip\", parseInt(e.target.value, 10) || undefined)\n                          }\n                          placeholder=\"0\"\n                          className=\"h-8 text-xs\"\n                          disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        />\n                      </div>\n\n                      <div className=\"space-y-1\">\n                        <Label htmlFor={`sample-size-${file.name}`} className=\"text-xs\">\n                          Sample Size\n                        </Label>\n                        <Input\n                          id={`sample-size-${file.name}`}\n                          type=\"number\"\n                          min=\"1\"\n                          value={csvOptions.sampleSize || \"\"}\n                          onChange={(e) =>\n                            handleCsvOptionChange(\n                              \"sampleSize\",\n                              parseInt(e.target.value, 10) || undefined\n                            )\n                          }\n                          placeholder=\"Auto\"\n                          className=\"h-8 text-xs\"\n                          disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        />\n                      </div>\n                    </div>\n\n                    <div className=\"space-y-1\">\n                      <Label htmlFor={`null-str-${file.name}`} className=\"text-xs\">\n                        NULL String\n                      </Label>\n                      <Input\n                        id={`null-str-${file.name}`}\n                        value={csvOptions.nullStr || \"\"}\n                        onChange={(e) => handleCsvOptionChange(\"nullStr\", e.target.value)}\n                        placeholder=\"Empty values treated as NULL\"\n                        className=\"h-8 text-xs\"\n                        disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                      />\n                      <p className=\"text-xs text-muted-foreground\">\n                        Values matching this string will be treated as NULL\n                      </p>\n                    </div>\n\n                    <div className=\"grid grid-cols-2 gap-3\">\n                      <div className=\"space-y-1\">\n                        <Label htmlFor={`date-format-${file.name}`} className=\"text-xs\">\n                          Date Format\n                        </Label>\n                        <Input\n                          id={`date-format-${file.name}`}\n                          value={csvOptions.dateFormat || \"\"}\n                          onChange={(e) => handleCsvOptionChange(\"dateFormat\", e.target.value)}\n                          placeholder=\"ISO 8601\"\n                          className=\"h-8 text-xs\"\n                          disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        />\n                      </div>\n\n                      <div className=\"space-y-1\">\n                        <Label htmlFor={`timestamp-format-${file.name}`} className=\"text-xs\">\n                          Timestamp Format\n                        </Label>\n                        <Input\n                          id={`timestamp-format-${file.name}`}\n                          value={csvOptions.timestampFormat || \"\"}\n                          onChange={(e) => handleCsvOptionChange(\"timestampFormat\", e.target.value)}\n                          placeholder=\"ISO 8601\"\n                          className=\"h-8 text-xs\"\n                          disabled={status.status === \"uploading\" || status.status === \"processing\"}\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              )}\n            </div>\n          )}\n\n          {status.status === \"uploading\" && status.progress !== undefined && (\n            <div className=\"flex flex-col space-y-2\">\n              <span className=\"text-sm text-gray-500\">Uploading... {status.progress}%</span>\n              <Progress value={status.progress} />\n            </div>\n          )}\n\n          {status.status === \"success\" && (\n            <div className=\"flex items-center gap-2 text-green-600 bg-green-50 p-2 rounded max-w-md \">\n              <FileCheck className=\"w-4 h-4\" />\n              <span className=\"text-sm\">Successfully imported</span>\n            </div>\n          )}\n\n          {status.status === \"error\" && status.error && (\n            <div className=\"flex items-center gap-2 text-red-600 bg-red-500/20 p-2 rounded max-w-md\">\n              <FileWarning className=\"w-4 h-4\" />\n              <span className=\"text-xs\">{status.error}</span>\n            </div>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst FileImporter: React.FC<FileImporterProps> = ({ isSheetOpen, setIsSheetOpen }) => {\n  const importFile = useDuckStore((s) => s.importFile);\n  const executeQuery = useDuckStore((s) => s.executeQuery);\n  const [activeTab, setActiveTab] = useState(\"upload\");\n  const [files, setFiles] = useState<FileWithPreview[]>([]);\n  const [tableNames, setTableNames] = useState<Record<string, string>>({});\n  const [isUploading, setIsUploading] = useState(false);\n  const [errors, setErrors] = useState<UploadError[]>([]);\n  const [importStates, setImportStates] = useState<Record<string, FileImportState>>({});\n  const [csvOptions, setCsvOptions] = useState<Record<string, CsvImportOptions>>({});\n  const [isDragActive, setIsDragActive] = useState(false);\n  const [importMode, setImportMode] = useState<\"table\" | \"view\">(\"table\");\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const abortControllerRef = useRef<AbortController | null>(null);\n\n  // URL import state\n  const [urlInput, setUrlInput] = useState(\"\");\n  const [urlTableName, setUrlTableName] = useState(\"\");\n  const [isUrlImporting, setIsUrlImporting] = useState(false);\n\n  // Preview state (Phase 2 - used in PreviewTable component)\n  const [isPreviewMode, setIsPreviewMode] = useState(false);\n  const [isPreviewing, setIsPreviewing] = useState(false);\n  const [previewData, setPreviewData] = useState<QueryResult | null>(null);\n  const [previewSource, setPreviewSource] = useState<\"file\" | \"url\" | null>(null);\n  const [previewFileName, setPreviewFileName] = useState<string>(\"\");\n  const [previewTableName, setPreviewTableName] = useState<string>(\"\");\n\n  // Schema customization state (Phase 2 - used in SchemaEditor component)\n  const [schemaColumns, setSchemaColumns] = useState<SchemaColumn[]>([]);\n  const [isSchemaCustomizing, setIsSchemaCustomizing] = useState(false);\n\n  // Query import state (Phase 2 - Query Import feature)\n  const [queryInput, setQueryInput] = useState(\"\");\n  const [queryTableName, setQueryTableName] = useState(\"\");\n  const [isQueryImporting, setIsQueryImporting] = useState(false);\n\n  // Default CSV import options\n  const defaultCsvOptions: CsvImportOptions = {\n    ignoreErrors: true,\n    nullPadding: true,\n    allVarchar: false,\n    header: true,\n    delimiter: \",\",\n    autoDetect: true,\n  };\n\n  const hasFilesToImport = useMemo(() => files.length > 0, [files]);\n\n  const allFilesSuccess = useMemo(() => {\n    if (!files.length) return false;\n    return files.every((file) => importStates[file.name]?.status === \"success\");\n  }, [files, importStates]);\n\n  const validateFile = (file: File): UploadError[] => {\n    const errors: UploadError[] = [];\n    const extension = file.name.split(\".\").pop()?.toLowerCase() as FileExtension;\n\n    if (!extension || !SUPPORTED_FILE_EXTENSIONS.includes(extension)) {\n      errors.push({\n        id: generateUUID(),\n        file: file.name,\n        message: `Unsupported file type: .${extension}`,\n        severity: \"error\",\n      });\n      toast.error(`Unsupported file type: .${extension}`);\n    }\n\n    if (file.size > MAX_FILE_SIZE) {\n      errors.push({\n        id: generateUUID(),\n        file: file.name,\n        message: `File exceeds maximum size of ${formatFileSize(MAX_FILE_SIZE)}`,\n        severity: \"error\",\n      });\n      toast.warning(`File exceeds maximum size of ${formatFileSize(MAX_FILE_SIZE)}`);\n    }\n\n    return errors;\n  };\n\n  const updateImportState = (fileName: string, state: Partial<FileImportState>) => {\n    setImportStates((prev) => ({\n      ...prev,\n      [fileName]: {\n        ...prev[fileName],\n        ...state,\n      },\n    }));\n  };\n\n  const onFileChange = useCallback(\n    (newFiles: File[]) => {\n      setErrors([]);\n      const newErrors: UploadError[] = [];\n      const validFiles: FileWithPreview[] = [];\n\n      newFiles.forEach((file) => {\n        const fileErrors = validateFile(file);\n        if (fileErrors.length > 0) {\n          newErrors.push(...fileErrors);\n        } else {\n          validFiles.push(\n            Object.assign(file, {\n              preview: URL.createObjectURL(file),\n            })\n          );\n        }\n      });\n\n      if (newErrors.length > 0) {\n        setErrors(newErrors);\n      }\n\n      setFiles((prevFiles) => [...prevFiles, ...validFiles]);\n\n      const newTableNames = validFiles.reduce<Record<string, string>>(\n        (acc, file) => ({\n          ...acc,\n          [file.name]: file.name\n            .replace(/\\.[^/.]+$/, \"\")\n            .replace(/[^a-zA-Z0-9_]/g, \"_\")\n            .toLowerCase(),\n        }),\n        {}\n      );\n\n      setTableNames((prev) => ({ ...prev, ...newTableNames }));\n\n      // Initialize CSV options for any CSV files\n      const newCsvOptions = validFiles.reduce<Record<string, CsvImportOptions>>((acc, file) => {\n        const extension = file.name.split(\".\").pop()?.toLowerCase();\n        if (extension === \"csv\") {\n          acc[file.name] = { ...defaultCsvOptions };\n        }\n        return acc;\n      }, {});\n\n      setCsvOptions((prev) => ({ ...prev, ...newCsvOptions }));\n\n      const initialImportStates = validFiles.reduce<Record<string, FileImportState>>(\n        (acc, file) => {\n          acc[file.name] = {\n            fileName: file.name,\n            status: \"pending\",\n          };\n          return acc;\n        },\n        {}\n      );\n\n      setImportStates((prev) => ({ ...prev, ...initialImportStates }));\n    },\n    [toast]\n  );\n\n  const handleDrop = useCallback(\n    (event: React.DragEvent<HTMLDivElement>) => {\n      event.preventDefault();\n      setIsDragActive(false);\n      if (!event.dataTransfer.files || event.dataTransfer.files.length === 0) return;\n      onFileChange(Array.from(event.dataTransfer.files));\n    },\n    [onFileChange]\n  );\n\n  const handleDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    setIsDragActive(true);\n  }, []);\n\n  const handleDragLeave = useCallback((event: React.DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    setIsDragActive(false);\n  }, []);\n\n  const handleFileInputChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      if (event.target.files && event.target.files.length > 0) {\n        onFileChange(Array.from(event.target.files));\n      }\n      if (fileInputRef.current) {\n        fileInputRef.current.value = \"\";\n      }\n    },\n    [onFileChange]\n  );\n\n  const handleFileUpload = async () => {\n    setIsUploading(true);\n    setErrors([]);\n\n    try {\n      const uploadPromises = files.map(async (file) => {\n        const state = importStates[file.name];\n        if (state?.status === \"success\") return;\n\n        const cleanTableName = tableNames[file.name];\n        try {\n          tableNameSchema.parse(cleanTableName);\n        } catch (error) {\n          const errorMessage =\n            error instanceof z.ZodError ? error.issues[0].message : \"Invalid table name\";\n          setErrors((prev) => [\n            ...prev,\n            {\n              id: generateUUID(),\n              message: errorMessage,\n              file: file.name,\n              severity: \"error\",\n            },\n          ]);\n          updateImportState(file.name, {\n            status: \"error\",\n            error: errorMessage,\n          });\n          toast.error(`Invalid table name for ${file.name}`);\n          return;\n        }\n\n        updateImportState(file.name, { status: \"processing\" });\n\n        try {\n          const fileType = file.name.split(\".\").pop()?.toLowerCase() as FileExtension;\n          const arrayBuffer = await file.arrayBuffer();\n\n          // Add options for import\n          const importOptions: Record<string, unknown> = {\n            importMode, // \"table\" or \"view\"\n          };\n          if (fileType === \"csv\" && csvOptions[file.name]) {\n            importOptions.csv = csvOptions[file.name];\n          }\n\n          await importFile(\n            file.name,\n            arrayBuffer,\n            cleanTableName,\n            fileType,\n            undefined,\n            importOptions\n          );\n          updateImportState(file.name, { status: \"success\" });\n          toast.success(`Successfully imported ${file.name}`);\n        } catch (e) {\n          const errorMessage = e instanceof Error ? e.message : \"Unknown error\";\n          updateImportState(file.name, {\n            status: \"error\",\n            error: errorMessage,\n          });\n          setErrors((prev) => [\n            ...prev,\n            {\n              id: generateUUID(),\n              message: errorMessage,\n              file: errorMessage === \"File processing aborted\" ? undefined : file.name,\n              severity: \"error\",\n            },\n          ]);\n          toast.error(`Error importing ${file.name}`);\n        }\n      });\n\n      // Run promises with concurrency control\n      const concurrencyQueue = [];\n      for (let i = 0; i < uploadPromises.length; i += MAX_CONCURRENT_UPLOADS) {\n        const chunk = uploadPromises.slice(i, i + MAX_CONCURRENT_UPLOADS);\n        concurrencyQueue.push(Promise.all(chunk));\n      }\n\n      await Promise.all(concurrencyQueue);\n\n      if (allFilesSuccess) {\n        setIsSheetOpen(false);\n        setFiles([]);\n        setTableNames({});\n        setImportStates({});\n        toast.success(\"All files imported successfully\");\n      }\n    } catch (e) {\n      console.error(\"Error uploading: \", e);\n      toast.error(\"Error uploading files\");\n    } finally {\n      setIsUploading(false);\n    }\n  };\n\n  const handleCancelUpload = useCallback(() => {\n    abortControllerRef.current?.abort();\n    setIsUploading(false);\n    toast.warning(\"Upload cancelled\");\n  }, [toast]);\n\n  const removeFile = useCallback(\n    (fileName: string) => {\n      setFiles((prev) => prev.filter((file) => file.name !== fileName));\n      setTableNames((prev) => {\n        const newNames = { ...prev };\n        delete newNames[fileName];\n        return newNames;\n      });\n      setErrors((prev) => prev.filter((error) => error.file !== fileName));\n      setImportStates((prev) => {\n        const newStates = { ...prev };\n        delete newStates[fileName];\n        return newStates;\n      });\n\n      const file = files.find((f) => f.name === fileName);\n      if (file?.preview) {\n        URL.revokeObjectURL(file.preview);\n      }\n\n      toast.info(`Removed ${fileName}`);\n    },\n    [files, toast]\n  );\n\n  const retryFileUpload = useCallback(\n    (fileName: string) => {\n      setImportStates((prev) => ({\n        ...prev,\n        [fileName]: {\n          ...prev[fileName],\n          status: \"pending\",\n          error: undefined,\n        },\n      }));\n      toast.info(`Retrying upload for ${fileName}`);\n    },\n    [toast]\n  );\n\n  // Preview handlers - Phase 2\n  const handlePreviewUrl = async (url: string, tableName: string) => {\n    setIsPreviewing(true);\n    setErrors([]);\n\n    try {\n      const urlPath = url.split(\"?\")[0];\n      const extension = urlPath.split(\".\").pop()?.toLowerCase();\n\n      let previewQuery = \"\";\n      if (extension === \"csv\") {\n        previewQuery = `SELECT * FROM read_csv('${url}', auto_detect=true, header=true) LIMIT ${PREVIEW_ROW_LIMIT}`;\n      } else if (extension === \"json\") {\n        previewQuery = `SELECT * FROM read_json('${url}', auto_detect=true) LIMIT ${PREVIEW_ROW_LIMIT}`;\n      } else if (extension === \"parquet\") {\n        previewQuery = `SELECT * FROM read_parquet('${url}') LIMIT ${PREVIEW_ROW_LIMIT}`;\n      } else {\n        throw new Error(`Unsupported file type for preview: .${extension}`);\n      }\n\n      const result = await executeQuery(previewQuery);\n      if (result && !result.error) {\n        setPreviewData(result);\n        setPreviewSource(\"url\");\n        setPreviewFileName(url);\n        setPreviewTableName(tableName);\n        setIsPreviewMode(true);\n\n        // Initialize schema columns from preview\n        const initialSchema: SchemaColumn[] = result.columns.map((col, idx) => ({\n          originalName: col,\n          newName: col,\n          type: result.columnTypes[idx] || \"VARCHAR\",\n          included: true,\n        }));\n        setSchemaColumns(initialSchema);\n      }\n    } catch (e) {\n      const errorMessage = e instanceof Error ? e.message : \"Unknown error\";\n      toast.error(`Preview failed: ${errorMessage}`);\n      setErrors([\n        {\n          id: generateUUID(),\n          message: errorMessage,\n          severity: \"error\",\n        },\n      ]);\n    } finally {\n      setIsPreviewing(false);\n    }\n  };\n\n  // Wrapper for preview with validation\n  const handlePreview = async () => {\n    if (!urlInput.trim()) {\n      toast.error(\"Please enter a URL\");\n      return;\n    }\n\n    if (!urlTableName.trim()) {\n      toast.error(\"Please enter a table name\");\n      return;\n    }\n\n    try {\n      tableNameSchema.parse(urlTableName);\n    } catch (error) {\n      const errorMessage =\n        error instanceof z.ZodError ? error.issues[0].message : \"Invalid table name\";\n      toast.error(errorMessage);\n      return;\n    }\n\n    await handlePreviewUrl(urlInput, urlTableName);\n  };\n\n  // Exit preview mode\n  const handleBackFromPreview = () => {\n    setIsPreviewMode(false);\n    setPreviewData(null);\n    setPreviewSource(null);\n    setPreviewFileName(\"\");\n    setPreviewTableName(\"\");\n    setSchemaColumns([]);\n  };\n\n  // Import from preview (with optional schema customization)\n  const handleImportFromPreview = async () => {\n    if (!previewFileName || !previewTableName) return;\n\n    setIsUrlImporting(true);\n    setErrors([]);\n\n    try {\n      if (previewSource === \"url\") {\n        const url = previewFileName;\n        const urlPath = url.split(\"?\")[0];\n        const extension = urlPath.split(\".\").pop()?.toLowerCase();\n\n        // Build column selection and casting based on schema customization\n        const includedColumns = schemaColumns.filter((col) => col.included);\n        const hasSchemaChanges = schemaColumns.some(\n          (col) => col.newName !== col.originalName || !col.included\n        );\n\n        let columnSelection = \"*\";\n        if (hasSchemaChanges && includedColumns.length > 0) {\n          columnSelection = includedColumns\n            .map((col) => {\n              if (col.newName !== col.originalName) {\n                return `\"${col.originalName}\" AS \"${col.newName}\"`;\n              }\n              return `\"${col.originalName}\"`;\n            })\n            .join(\", \");\n        }\n\n        let query = \"\";\n        const createType = importMode === \"view\" ? \"VIEW\" : \"TABLE\";\n        const resultType = importMode === \"view\" ? \"view\" : \"table\";\n\n        if (extension === \"csv\") {\n          query = `CREATE OR REPLACE ${createType} ${previewTableName} AS SELECT ${columnSelection} FROM read_csv('${url}', auto_detect=true, ignore_errors=true, header=true)`;\n        } else if (extension === \"json\") {\n          query = `CREATE OR REPLACE ${createType} ${previewTableName} AS SELECT ${columnSelection} FROM read_json('${url}', auto_detect=true, ignore_errors=true)`;\n        } else if (extension === \"parquet\") {\n          query = `CREATE OR REPLACE ${createType} ${previewTableName} AS SELECT ${columnSelection} FROM read_parquet('${url}')`;\n        } else {\n          throw new Error(`Unsupported file type: .${extension}`);\n        }\n\n        await executeQuery(query);\n        toast.success(`Successfully created ${resultType} '${previewTableName}'`);\n\n        // Reset state and close sheet\n        handleBackFromPreview();\n        setIsSheetOpen(false);\n      }\n    } catch (e) {\n      const errorMessage = e instanceof Error ? e.message : \"Unknown error\";\n      setErrors([\n        {\n          id: generateUUID(),\n          message: errorMessage,\n          severity: \"error\",\n        },\n      ]);\n      toast.error(`Failed to import: ${errorMessage}`);\n    } finally {\n      setIsUrlImporting(false);\n    }\n  };\n\n  // Schema customization handlers\n  const handleToggleColumn = (originalName: string) => {\n    setSchemaColumns((prev) =>\n      prev.map((col) =>\n        col.originalName === originalName ? { ...col, included: !col.included } : col\n      )\n    );\n  };\n\n  const handleRenameColumn = (originalName: string, newName: string) => {\n    setSchemaColumns((prev) =>\n      prev.map((col) => (col.originalName === originalName ? { ...col, newName } : col))\n    );\n  };\n\n  const handleChangeColumnType = (originalName: string, type: string) => {\n    setSchemaColumns((prev) =>\n      prev.map((col) => (col.originalName === originalName ? { ...col, type } : col))\n    );\n  };\n\n  // Handle URL import\n  const handleUrlImport = async () => {\n    if (!urlInput.trim()) {\n      toast.error(\"Please enter a URL\");\n      return;\n    }\n\n    if (!urlTableName.trim()) {\n      toast.error(\"Please enter a table name\");\n      return;\n    }\n\n    try {\n      tableNameSchema.parse(urlTableName);\n    } catch (error) {\n      const errorMessage =\n        error instanceof z.ZodError ? error.issues[0].message : \"Invalid table name\";\n      toast.error(errorMessage);\n      return;\n    }\n\n    setIsUrlImporting(true);\n    setErrors([]);\n\n    try {\n      // Detect file type from URL\n      const url = urlInput.trim();\n      const urlPath = url.split(\"?\")[0]; // Remove query params\n      const extension = urlPath.split(\".\").pop()?.toLowerCase();\n\n      let query = \"\";\n\n      // Build import query based on file type and import mode\n      const createType = importMode === \"view\" ? \"VIEW\" : \"TABLE\";\n      const resultType = importMode === \"view\" ? \"view\" : \"table\";\n\n      if (extension === \"csv\") {\n        query = `CREATE OR REPLACE ${createType} ${urlTableName} AS SELECT * FROM read_csv('${url}', auto_detect=true, ignore_errors=true, header=true)`;\n      } else if (extension === \"json\") {\n        query = `CREATE OR REPLACE ${createType} ${urlTableName} AS SELECT * FROM read_json('${url}', auto_detect=true, ignore_errors=true)`;\n      } else if (extension === \"parquet\") {\n        query = `CREATE OR REPLACE ${createType} ${urlTableName} AS SELECT * FROM read_parquet('${url}')`;\n      } else {\n        throw new Error(`Unsupported file type: .${extension}. Supported: CSV, JSON, Parquet`);\n      }\n\n      await executeQuery(query);\n\n      toast.success(`Successfully created ${resultType} '${urlTableName}' from URL`);\n      setUrlInput(\"\");\n      setUrlTableName(\"\");\n      setIsSheetOpen(false);\n    } catch (e) {\n      const errorMessage = e instanceof Error ? e.message : \"Unknown error\";\n      setErrors([\n        {\n          id: generateUUID(),\n          message: errorMessage,\n          severity: \"error\",\n        },\n      ]);\n      toast.error(`Failed to import from URL: ${errorMessage}`);\n    } finally {\n      setIsUrlImporting(false);\n    }\n  };\n\n  // Handle Query Import\n  const handleQueryImport = async () => {\n    if (!queryInput.trim()) {\n      toast.error(\"Please enter a SQL query\");\n      return;\n    }\n\n    if (!queryTableName.trim()) {\n      toast.error(\"Please enter a table name\");\n      return;\n    }\n\n    try {\n      tableNameSchema.parse(queryTableName);\n    } catch (error) {\n      const errorMessage =\n        error instanceof z.ZodError ? error.issues[0].message : \"Invalid table name\";\n      toast.error(errorMessage);\n      return;\n    }\n\n    setIsQueryImporting(true);\n    setErrors([]);\n\n    try {\n      const userQuery = queryInput.trim();\n      const createType = importMode === \"view\" ? \"VIEW\" : \"TABLE\";\n      const resultType = importMode === \"view\" ? \"view\" : \"table\";\n\n      // Wrap user query in CREATE statement\n      const wrappedQuery = `CREATE OR REPLACE ${createType} ${queryTableName} AS ${userQuery}`;\n\n      await executeQuery(wrappedQuery);\n\n      toast.success(`Successfully created ${resultType} '${queryTableName}' from query result`);\n      setQueryInput(\"\");\n      setQueryTableName(\"\");\n      setIsSheetOpen(false);\n    } catch (e) {\n      const errorMessage = e instanceof Error ? e.message : \"Unknown error\";\n      setErrors([\n        {\n          id: generateUUID(),\n          message: errorMessage,\n          severity: \"error\",\n        },\n      ]);\n      toast.error(`Failed to execute query: ${errorMessage}`);\n    } finally {\n      setIsQueryImporting(false);\n    }\n  };\n\n  useEffect(() => {\n    return () => {\n      files.forEach((file) => {\n        if (file.preview) {\n          URL.revokeObjectURL(file.preview);\n        }\n      });\n    };\n  }, [files]);\n\n  return (\n    <Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>\n      <SheetContent className=\"xl:w-[800px] sm:w-full sm:max-w-full overflow-y-auto\">\n        <SheetHeader>\n          <SheetTitle>{isPreviewMode ? \"Preview Data\" : \"Import Data\"}</SheetTitle>\n        </SheetHeader>\n        <Separator className=\"my-4\" />\n\n        {/* Preview Mode UI */}\n        {isPreviewMode && previewData && (\n          <div className=\"space-y-4\">\n            {/* Header with file info and navigation */}\n            <Card>\n              <CardContent className=\"p-4\">\n                <div className=\"flex items-start justify-between mb-3\">\n                  <div className=\"flex-1\">\n                    <h3 className=\"font-semibold text-lg mb-1\">\n                      Preview:{\" \"}\n                      {previewFileName.length > 50\n                        ? `...${previewFileName.slice(-47)}`\n                        : previewFileName}\n                    </h3>\n                    <p className=\"text-sm text-muted-foreground\">\n                      Table Name: <span className=\"font-mono\">{previewTableName}</span>\n                    </p>\n                    <p className=\"text-xs text-muted-foreground mt-1\">\n                      Showing first {PREVIEW_ROW_LIMIT} rows • {previewData.rowCount} total rows\n                    </p>\n                  </div>\n                  <Button variant=\"ghost\" size=\"sm\" onClick={handleBackFromPreview}>\n                    <ArrowLeft className=\"w-4 h-4 mr-2\" />\n                    Back\n                  </Button>\n                </div>\n              </CardContent>\n            </Card>\n\n            {/* Preview Table */}\n            <Card>\n              <CardContent className=\"p-4\">\n                <DuckUITable data={previewData.data} initialPageSize={20} tableHeight=\"400px\" />\n              </CardContent>\n            </Card>\n\n            {/* Schema Customization */}\n            <Card>\n              <CardContent className=\"p-4\">\n                <div className=\"space-y-4\">\n                  <div className=\"flex items-center justify-between\">\n                    <div>\n                      <h4 className=\"font-semibold text-base\">Schema Customization</h4>\n                      <p className=\"text-sm text-muted-foreground\">\n                        Customize column names, types, and visibility before importing\n                      </p>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      onClick={() => setIsSchemaCustomizing(!isSchemaCustomizing)}\n                    >\n                      {isSchemaCustomizing ? (\n                        <>\n                          <ChevronUp className=\"w-4 h-4 mr-2\" />\n                          Hide\n                        </>\n                      ) : (\n                        <>\n                          <ChevronDown className=\"w-4 h-4 mr-2\" />\n                          Customize\n                        </>\n                      )}\n                    </Button>\n                  </div>\n\n                  {isSchemaCustomizing && (\n                    <div className=\"border rounded-lg overflow-hidden\">\n                      <div className=\"overflow-x-auto\">\n                        <table className=\"w-full text-sm\">\n                          <thead className=\"bg-muted\">\n                            <tr>\n                              <th className=\"p-2 text-left w-12\">Include</th>\n                              <th className=\"p-2 text-left\">Original Name</th>\n                              <th className=\"p-2 text-left\">New Name</th>\n                              <th className=\"p-2 text-left w-48\">Type</th>\n                            </tr>\n                          </thead>\n                          <tbody className=\"divide-y\">\n                            {schemaColumns.map((col) => (\n                              <tr\n                                key={col.originalName}\n                                className={!col.included ? \"opacity-50\" : \"\"}\n                              >\n                                <td className=\"p-2\">\n                                  <Checkbox\n                                    checked={col.included}\n                                    onCheckedChange={() => handleToggleColumn(col.originalName)}\n                                  />\n                                </td>\n                                <td className=\"p-2\">\n                                  <code className=\"text-xs bg-muted px-2 py-1 rounded\">\n                                    {col.originalName}\n                                  </code>\n                                </td>\n                                <td className=\"p-2\">\n                                  <Input\n                                    value={col.newName}\n                                    onChange={(e) =>\n                                      handleRenameColumn(col.originalName, e.target.value)\n                                    }\n                                    disabled={!col.included}\n                                    className=\"h-8 text-xs\"\n                                    placeholder=\"Column name\"\n                                  />\n                                </td>\n                                <td className=\"p-2\">\n                                  <Select\n                                    value={col.type}\n                                    onValueChange={(value) =>\n                                      handleChangeColumnType(col.originalName, value)\n                                    }\n                                    disabled={!col.included}\n                                  >\n                                    <SelectTrigger className=\"h-8 text-xs\">\n                                      <SelectValue />\n                                    </SelectTrigger>\n                                    <SelectContent>\n                                      {DUCKDB_TYPES.map((type) => (\n                                        <SelectItem key={type} value={type} className=\"text-xs\">\n                                          {type}\n                                        </SelectItem>\n                                      ))}\n                                    </SelectContent>\n                                  </Select>\n                                </td>\n                              </tr>\n                            ))}\n                          </tbody>\n                        </table>\n                      </div>\n                      <div className=\"p-3 bg-muted/50 text-xs text-muted-foreground\">\n                        <p>\n                          {schemaColumns.filter((col) => col.included).length} of{\" \"}\n                          {schemaColumns.length} columns will be imported\n                        </p>\n                      </div>\n                    </div>\n                  )}\n                </div>\n              </CardContent>\n            </Card>\n\n            {/* Action Buttons */}\n            <div className=\"flex gap-2 justify-end\">\n              <Button variant=\"outline\" onClick={handleBackFromPreview}>\n                Cancel\n              </Button>\n              <Button onClick={handleImportFromPreview} disabled={isUrlImporting}>\n                {isUrlImporting ? (\n                  <>\n                    <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n                    Importing...\n                  </>\n                ) : (\n                  <>\n                    <Check className=\"w-4 h-4 mr-2\" />\n                    Import Table\n                  </>\n                )}\n              </Button>\n            </div>\n\n            {/* Errors */}\n            {errors.length > 0 && (\n              <div className=\"space-y-2\">\n                {errors.map((error) => {\n                  const suggestion = getErrorSuggestion(error.message);\n                  return (\n                    <Alert key={error.id} variant=\"destructive\">\n                      <AlertTitle className=\"flex items-center gap-2\">\n                        <AlertTriangle className=\"h-4 w-4\" />\n                        Error\n                      </AlertTitle>\n                      <AlertDescription>\n                        <div className=\"space-y-2\">\n                          <p>{error.message}</p>\n                          {suggestion && (\n                            <p className=\"text-sm opacity-90 border-t pt-2 mt-2\">\n                              <strong>Suggestion:</strong> {suggestion}\n                            </p>\n                          )}\n                        </div>\n                      </AlertDescription>\n                    </Alert>\n                  );\n                })}\n              </div>\n            )}\n          </div>\n        )}\n\n        {/* Import Tabs (when not in preview mode) */}\n        {!isPreviewMode && (\n          <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n            <TabsList className=\"grid w-full grid-cols-3\">\n              <TabsTrigger value=\"upload\" className=\"gap-2\">\n                <Upload className=\"w-4 h-4\" />\n                Upload Files\n              </TabsTrigger>\n              <TabsTrigger value=\"url\" className=\"gap-2\">\n                <LinkIcon className=\"w-4 h-4\" />\n                From URL\n              </TabsTrigger>\n              <TabsTrigger value=\"query\" className=\"gap-2\">\n                <Code className=\"w-4 h-4\" />\n                From Query\n              </TabsTrigger>\n            </TabsList>\n\n            {/* Upload Files Tab */}\n            <TabsContent value=\"upload\" className=\"space-y-4 mt-4\">\n              <CardContent className=\"space-y-4 p-0\">\n                <div\n                  onDrop={handleDrop}\n                  onDragOver={handleDragOver}\n                  onDragLeave={handleDragLeave}\n                  className={cn(\n                    \"border-2 border-dashed rounded-lg p-8 text-center cursor-pointer\",\n                    \"transition-colors duration-200 min-h-[200px] flex flex-col items-center justify-center\",\n                    isDragActive\n                      ? \"border-primary bg-primary/10\"\n                      : \"border-gray-300 hover:border-primary hover:bg-primary/10\"\n                  )}\n                >\n                  <Input\n                    type=\"file\"\n                    multiple\n                    hidden\n                    ref={fileInputRef}\n                    accept={Object.values(ACCEPTED_FILE_TYPES).flat().join(\",\")}\n                    onChange={handleFileInputChange}\n                  />\n                  <Upload\n                    className={cn(\n                      \"w-12 h-12 mb-4 mt-4\",\n                      isDragActive ? \"text-accent\" : \"text-muted-foreground\"\n                    )}\n                  />\n                  {isDragActive ? (\n                    <p className=\"text-blue-500 font-medium\">Drop the files here ...</p>\n                  ) : (\n                    <>\n                      <div className=\"space-y-2\">\n                        <p className=\"font-medium\">Drag & drop files here, or</p>\n                        <Button variant=\"outline\" onClick={() => fileInputRef.current?.click()}>\n                          Select Files\n                        </Button>\n                        <p className=\"text-sm text-gray-500\">\n                          Supported formats: CSV, JSON, Parquet, Arrow and DuckDB\n                        </p>\n                        <p className=\"text-xs text-gray-400\">\n                          Maximum file size: {formatFileSize(MAX_FILE_SIZE)}\n                        </p>\n                      </div>\n                    </>\n                  )}\n                </div>\n\n                {hasFilesToImport && (\n                  <div className=\"space-y-4\">\n                    <div className=\"flex items-center justify-between\">\n                      <h3 className=\"font-medium text-lg\">Files to Import</h3>\n                      <div className=\"flex items-center gap-2\">\n                        <span className=\"text-sm text-muted-foreground\">Mode:</span>\n                        <div className=\"flex rounded-md border\">\n                          <Button\n                            variant={importMode === \"table\" ? \"default\" : \"ghost\"}\n                            size=\"sm\"\n                            className=\"rounded-r-none h-7 text-xs\"\n                            onClick={() => setImportMode(\"table\")}\n                          >\n                            Import (Table)\n                          </Button>\n                          <Button\n                            variant={importMode === \"view\" ? \"default\" : \"ghost\"}\n                            size=\"sm\"\n                            className=\"rounded-l-none h-7 text-xs\"\n                            onClick={() => setImportMode(\"view\")}\n                          >\n                            Link (View)\n                          </Button>\n                        </div>\n                      </div>\n                    </div>\n                    {importMode === \"view\" && (\n                      <p className=\"text-xs text-muted-foreground\">\n                        Views reference the original file without copying data. Queries re-read the\n                        file each time, using less memory but may be slower.\n                      </p>\n                    )}\n                    <div className=\"space-y-3\">\n                      {files.map((file) => {\n                        const fileType = file.name.split(\".\").pop()?.toLowerCase();\n                        const isCsvFile = fileType === \"csv\";\n\n                        return (\n                          <FileDetails\n                            key={file.name}\n                            file={file}\n                            tableName={tableNames[file.name] || \"\"}\n                            onTableNameChange={(name) =>\n                              setTableNames((prev) => ({\n                                ...prev,\n                                [file.name]: name,\n                              }))\n                            }\n                            status={\n                              importStates[file.name] || {\n                                fileName: file.name,\n                                status: \"pending\",\n                              }\n                            }\n                            csvOptions={isCsvFile ? csvOptions[file.name] : undefined}\n                            onCsvOptionsChange={\n                              isCsvFile\n                                ? (options) =>\n                                    setCsvOptions((prev) => ({\n                                      ...prev,\n                                      [file.name]: options,\n                                    }))\n                                : undefined\n                            }\n                            onRemove={() => removeFile(file.name)}\n                            onRetry={() => retryFileUpload(file.name)}\n                          />\n                        );\n                      })}\n                    </div>\n                  </div>\n                )}\n\n                {errors.length > 0 && (\n                  <div className=\"space-y-2\">\n                    {errors.map((error) => {\n                      const suggestion = getErrorSuggestion(error.message);\n                      return (\n                        <Alert\n                          key={error.id}\n                          variant={error.severity === \"error\" ? \"destructive\" : \"default\"}\n                        >\n                          <AlertTitle className=\"flex items-center gap-2\">\n                            {error.severity === \"error\" ? (\n                              <AlertTriangle className=\"h-4 w-4\" />\n                            ) : (\n                              <FileWarning className=\"h-4 w-4\" />\n                            )}\n                            {error.severity === \"error\" ? \"Error\" : \"Warning\"}\n                          </AlertTitle>\n                          <AlertDescription>\n                            <div className=\"space-y-2\">\n                              <p>\n                                {error.file ? `${error.file}: ${error.message}` : error.message}\n                              </p>\n                              {suggestion && (\n                                <p className=\"text-sm opacity-90 border-t pt-2 mt-2\">\n                                  <strong>Suggestion:</strong> {suggestion}\n                                </p>\n                              )}\n                            </div>\n                          </AlertDescription>\n                        </Alert>\n                      );\n                    })}\n                  </div>\n                )}\n\n                {hasFilesToImport && (\n                  <div className=\"flex gap-2\">\n                    <Button onClick={handleFileUpload} disabled={isUploading} className=\"flex-1\">\n                      {isUploading ? (\n                        <>\n                          <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n                          Importing Files...\n                        </>\n                      ) : (\n                        <>\n                          <Upload className=\"w-4 h-4 mr-2\" />\n                          Import {files.length} {files.length === 1 ? \"File\" : \"Files\"}\n                        </>\n                      )}\n                    </Button>\n\n                    {isUploading && (\n                      <Button\n                        variant=\"destructive\"\n                        onClick={handleCancelUpload}\n                        className=\"whitespace-nowrap\"\n                      >\n                        <X className=\"w-4 h-4 mr-2\" />\n                        Cancel\n                      </Button>\n                    )}\n                  </div>\n                )}\n              </CardContent>\n            </TabsContent>\n\n            {/* URL Import Tab */}\n            <TabsContent value=\"url\" className=\"space-y-4 mt-4\">\n              <CardContent className=\"space-y-4 p-0\">\n                <div className=\"space-y-4\">\n                  <div>\n                    <h3 className=\"font-medium text-lg mb-4\">Import from URL</h3>\n                    <p className=\"text-sm text-muted-foreground mb-4\">\n                      DuckDB can directly read files from HTTP/HTTPS URLs. Supports CSV, JSON, and\n                      Parquet files.\n                    </p>\n                  </div>\n\n                  <div className=\"space-y-2\">\n                    <Label htmlFor=\"url-input\">File URL</Label>\n                    <Input\n                      id=\"url-input\"\n                      value={urlInput}\n                      onChange={(e) => setUrlInput(e.target.value)}\n                      placeholder=\"https://example.com/data.csv\"\n                      disabled={isUrlImporting || isPreviewing}\n                    />\n                    <p className=\"text-xs text-muted-foreground\">\n                      Enter a direct URL to a CSV, JSON, or Parquet file\n                    </p>\n                  </div>\n\n                  <div className=\"space-y-2\">\n                    <Label htmlFor=\"url-table-name\">Name</Label>\n                    <Input\n                      id=\"url-table-name\"\n                      value={urlTableName}\n                      onChange={(e) => setUrlTableName(e.target.value)}\n                      placeholder=\"my_table\"\n                      disabled={isUrlImporting || isPreviewing}\n                    />\n                  </div>\n\n                  <div className=\"space-y-2\">\n                    <Label>Import Mode</Label>\n                    <div className=\"flex gap-1\">\n                      <Button\n                        type=\"button\"\n                        variant={importMode === \"table\" ? \"default\" : \"outline\"}\n                        size=\"sm\"\n                        className=\"flex-1\"\n                        onClick={() => setImportMode(\"table\")}\n                        disabled={isUrlImporting || isPreviewing}\n                      >\n                        <Table className=\"h-4 w-4 mr-1.5\" />\n                        Table\n                      </Button>\n                      <Button\n                        type=\"button\"\n                        variant={importMode === \"view\" ? \"default\" : \"outline\"}\n                        size=\"sm\"\n                        className=\"flex-1\"\n                        onClick={() => setImportMode(\"view\")}\n                        disabled={isUrlImporting || isPreviewing}\n                      >\n                        <Link2 className=\"h-4 w-4 mr-1.5\" />\n                        View\n                      </Button>\n                    </div>\n                    <p className=\"text-xs text-muted-foreground\">\n                      {importMode === \"view\"\n                        ? \"View references URL directly (fresh data, less memory)\"\n                        : \"Table copies data into DuckDB (faster queries)\"}\n                    </p>\n                  </div>\n\n                  {errors.length > 0 && (\n                    <div className=\"space-y-2\">\n                      {errors.map((error) => {\n                        const suggestion = getErrorSuggestion(error.message);\n                        return (\n                          <Alert key={error.id} variant=\"destructive\">\n                            <AlertTitle className=\"flex items-center gap-2\">\n                              <AlertTriangle className=\"h-4 w-4\" />\n                              Error\n                            </AlertTitle>\n                            <AlertDescription>\n                              <div className=\"space-y-2\">\n                                <p>{error.message}</p>\n                                {suggestion && (\n                                  <p className=\"text-sm opacity-90 border-t pt-2 mt-2\">\n                                    <strong>Suggestion:</strong> {suggestion}\n                                  </p>\n                                )}\n                              </div>\n                            </AlertDescription>\n                          </Alert>\n                        );\n                      })}\n                    </div>\n                  )}\n\n                  <div className=\"flex gap-2\">\n                    <Button\n                      onClick={handlePreview}\n                      disabled={isUrlImporting || isPreviewing}\n                      variant=\"outline\"\n                      className=\"flex-1\"\n                    >\n                      {isPreviewing ? (\n                        <>\n                          <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n                          Loading Preview...\n                        </>\n                      ) : (\n                        <>\n                          <Eye className=\"w-4 h-4 mr-2\" />\n                          Preview\n                        </>\n                      )}\n                    </Button>\n                    <Button\n                      onClick={handleUrlImport}\n                      disabled={isUrlImporting || isPreviewing}\n                      className=\"flex-1\"\n                    >\n                      {isUrlImporting ? (\n                        <>\n                          <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n                          Importing...\n                        </>\n                      ) : (\n                        <>\n                          <LinkIcon className=\"w-4 h-4 mr-2\" />\n                          Import Directly\n                        </>\n                      )}\n                    </Button>\n                  </div>\n                </div>\n              </CardContent>\n            </TabsContent>\n\n            {/* Query Import Tab */}\n            <TabsContent value=\"query\" className=\"space-y-4 mt-4\">\n              <CardContent className=\"space-y-4 p-0\">\n                <div className=\"space-y-4\">\n                  <div>\n                    <h3 className=\"font-medium text-lg mb-4\">Import from Query Result</h3>\n                    <p className=\"text-sm text-muted-foreground mb-4\">\n                      Execute a SQL query and create a table from the result. Use DuckDB functions\n                      like read_csv, read_json, read_parquet, or query existing tables.\n                    </p>\n                  </div>\n\n                  <div className=\"space-y-2\">\n                    <Label htmlFor=\"query-input\">SQL Query</Label>\n                    <Textarea\n                      id=\"query-input\"\n                      value={queryInput}\n                      onChange={(e) => setQueryInput(e.target.value)}\n                      placeholder=\"SELECT * FROM read_csv('https://example.com/data.csv')\"\n                      disabled={isQueryImporting}\n                      rows={8}\n                      className=\"font-mono text-sm\"\n                    />\n                    <p className=\"text-xs text-muted-foreground\">\n                      Enter any SELECT query. The result will be saved to a new table.\n                    </p>\n                  </div>\n\n                  <div className=\"space-y-2\">\n                    <Label htmlFor=\"query-table-name\">Name</Label>\n                    <Input\n                      id=\"query-table-name\"\n                      value={queryTableName}\n                      onChange={(e) => setQueryTableName(e.target.value)}\n                      placeholder=\"my_table\"\n                      disabled={isQueryImporting}\n                    />\n                  </div>\n\n                  <div className=\"space-y-2\">\n                    <Label>Save As</Label>\n                    <div className=\"flex gap-1\">\n                      <Button\n                        type=\"button\"\n                        variant={importMode === \"table\" ? \"default\" : \"outline\"}\n                        size=\"sm\"\n                        className=\"flex-1\"\n                        onClick={() => setImportMode(\"table\")}\n                        disabled={isQueryImporting}\n                      >\n                        <Table className=\"h-4 w-4 mr-1.5\" />\n                        Table\n                      </Button>\n                      <Button\n                        type=\"button\"\n                        variant={importMode === \"view\" ? \"default\" : \"outline\"}\n                        size=\"sm\"\n                        className=\"flex-1\"\n                        onClick={() => setImportMode(\"view\")}\n                        disabled={isQueryImporting}\n                      >\n                        <Link2 className=\"h-4 w-4 mr-1.5\" />\n                        View\n                      </Button>\n                    </div>\n                    <p className=\"text-xs text-muted-foreground\">\n                      {importMode === \"view\"\n                        ? \"View re-runs query each time (always fresh)\"\n                        : \"Table stores result (faster queries)\"}\n                    </p>\n                  </div>\n\n                  {/* Examples */}\n                  <div className=\"rounded-lg border p-4 bg-muted/50\">\n                    <h4 className=\"font-medium text-sm mb-2\">Example Queries:</h4>\n                    <div className=\"space-y-2 text-xs font-mono\">\n                      <p className=\"text-muted-foreground\">\n                        <span className=\"text-foreground\">• Import from URL:</span>\n                        <br />\n                        <code className=\"block ml-4 mt-1\">\n                          SELECT * FROM read_csv('https://example.com/data.csv')\n                        </code>\n                      </p>\n                      <p className=\"text-muted-foreground\">\n                        <span className=\"text-foreground\">• Filter data:</span>\n                        <br />\n                        <code className=\"block ml-4 mt-1\">\n                          SELECT * FROM read_json('data.json') WHERE age &gt; 21\n                        </code>\n                      </p>\n                      <p className=\"text-muted-foreground\">\n                        <span className=\"text-foreground\">• Join tables:</span>\n                        <br />\n                        <code className=\"block ml-4 mt-1\">\n                          SELECT a.*, b.name FROM table1 a JOIN table2 b ON a.id = b.id\n                        </code>\n                      </p>\n                    </div>\n                  </div>\n\n                  {errors.length > 0 && (\n                    <div className=\"space-y-2\">\n                      {errors.map((error) => {\n                        const suggestion = getErrorSuggestion(error.message);\n                        return (\n                          <Alert key={error.id} variant=\"destructive\">\n                            <AlertTitle className=\"flex items-center gap-2\">\n                              <AlertTriangle className=\"h-4 w-4\" />\n                              Error\n                            </AlertTitle>\n                            <AlertDescription>\n                              <div className=\"space-y-2\">\n                                <p>{error.message}</p>\n                                {suggestion && (\n                                  <p className=\"text-sm opacity-90 border-t pt-2 mt-2\">\n                                    <strong>Suggestion:</strong> {suggestion}\n                                  </p>\n                                )}\n                              </div>\n                            </AlertDescription>\n                          </Alert>\n                        );\n                      })}\n                    </div>\n                  )}\n\n                  <Button\n                    onClick={handleQueryImport}\n                    disabled={isQueryImporting}\n                    className=\"w-full\"\n                  >\n                    {isQueryImporting ? (\n                      <>\n                        <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n                        Executing Query...\n                      </>\n                    ) : (\n                      <>\n                        <Code className=\"w-4 h-4 mr-2\" />\n                        Execute and Import\n                      </>\n                    )}\n                  </Button>\n                </div>\n              </CardContent>\n            </TabsContent>\n          </Tabs>\n        )}\n      </SheetContent>\n    </Sheet>\n  );\n};\n\nexport default FileImporter;\n"
  },
  {
    "path": "src/components/explorer/TreeNode.tsx",
    "content": "// TreeNode.tsx\nimport React, { useState, useCallback, useMemo, useEffect } from \"react\";\nimport {\n  ChevronRight,\n  ChevronDown,\n  Database,\n  Table,\n  FileSpreadsheet,\n  Trash,\n  TerminalIcon,\n  MoreVertical,\n  Loader2,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { toast } from \"sonner\";\nimport { useDuckStore, type ColumnStats } from \"@/store\";\nimport { ColumnNode } from \"./ColumnNode\";\n\nexport interface TreeNodeData {\n  name: string;\n  type: \"database\" | \"table\" | \"view\";\n  children?: TreeNodeData[];\n  query?: string;\n}\n\ninterface TreeNodeProps {\n  node: TreeNodeData;\n  level: number;\n  searchTerm: string;\n  parentDatabaseName?: string;\n  refreshData: () => void;\n}\n\nconst TreeNode: React.FC<TreeNodeProps> = React.memo(\n  ({ node, level, searchTerm, parentDatabaseName, refreshData }) => {\n    const [isOpen, setIsOpen] = useState(false);\n    const [columnStats, setColumnStats] = useState<ColumnStats[]>([]);\n    const [isLoadingStats, setIsLoadingStats] = useState(false);\n    const toggleOpen = useCallback(() => setIsOpen((open) => !open), []);\n\n    const createTab = useDuckStore((s) => s.createTab);\n    const executeQuery = useDuckStore((s) => s.executeQuery);\n    const deleteTable = useDuckStore((s) => s.deleteTable);\n    const fetchDatabasesAndTablesInfo = useDuckStore((s) => s.fetchDatabasesAndTablesInfo);\n    const fetchTableColumnStats = useDuckStore((s) => s.fetchTableColumnStats);\n\n    // Fetch column stats when table is expanded\n    useEffect(() => {\n      const loadColumnStats = async () => {\n        if (isOpen && node.type === \"table\" && parentDatabaseName && columnStats.length === 0) {\n          setIsLoadingStats(true);\n          try {\n            const stats = await fetchTableColumnStats(parentDatabaseName, node.name);\n            setColumnStats(stats);\n          } catch (error) {\n            console.error(\"Failed to fetch column stats:\", error);\n          } finally {\n            setIsLoadingStats(false);\n          }\n        }\n      };\n\n      loadColumnStats();\n    }, [\n      isOpen,\n      node.type,\n      node.name,\n      parentDatabaseName,\n      columnStats.length,\n      fetchTableColumnStats,\n    ]);\n\n    const getIcon = useMemo(() => {\n      switch (node.type) {\n        case \"database\":\n          return <Database className=\"w-4 h-4 mr-2\" />;\n        case \"table\":\n        case \"view\":\n          return <Table className=\"w-4 h-4 mr-2\" />;\n        default:\n          return null;\n      }\n    }, [node.type]);\n\n    const handleQueryData = useCallback(\n      (databaseName: string, tableName: string) => async () => {\n        const query = `SELECT * FROM \"${databaseName}\".\"${tableName}\" LIMIT 100`;\n\n        createTab(\"sql\", query, tableName);\n\n        const tabId = useDuckStore.getState().activeTabId;\n        if (tabId) {\n          await executeQuery(query, tabId);\n        }\n        toast.success(`Querying table \"${tableName}\"`);\n      },\n      [createTab, executeQuery, toast]\n    );\n\n    const handleDeleteTable = useCallback(\n      (databaseName: string, tableName: string) => async () => {\n        try {\n          await deleteTable(tableName, databaseName);\n          toast.success(`Table \"${tableName}\" deleted successfully.`);\n          await fetchDatabasesAndTablesInfo();\n          refreshData();\n        } catch (error) {\n          toast.error(\n            `Failed to delete table \"${tableName}\": ${\n              error instanceof Error ? error.message : \"Unknown error\"\n            }`\n          );\n        }\n      },\n      [deleteTable, toast, fetchDatabasesAndTablesInfo, refreshData]\n    );\n\n    const handleShowSchema = useCallback(\n      (databaseName: string, tableName: string) => async () => {\n        const query = `DESCRIBE \"${databaseName}\".\"${tableName}\"`;\n\n        createTab(\"sql\", query, `${tableName} Schema`);\n\n        const tabId = useDuckStore.getState().activeTabId;\n        if (tabId) {\n          await executeQuery(query, tabId);\n        }\n        toast.success(`Showing schema for table \"${tableName}\"`);\n      },\n      [createTab, executeQuery, toast]\n    );\n\n    const contextMenuOptions = useMemo(\n      () => ({\n        database: [],\n        table: [\n          {\n            label: \"Query Table\",\n            icon: <TerminalIcon className=\"w-4 h-4 mr-2\" />,\n            action: parentDatabaseName\n              ? handleQueryData(parentDatabaseName, node.name)\n              : () => {\n                  toast.error(\"Parent database name is undefined.\");\n                },\n          },\n          {\n            label: \"Show Schema\",\n            icon: <FileSpreadsheet className=\"w-4 h-4 mr-2\" />,\n            action: parentDatabaseName\n              ? handleShowSchema(parentDatabaseName, node.name)\n              : () => {\n                  toast.error(\"Parent database name is undefined.\");\n                },\n          },\n          {\n            label: \"Delete Table\",\n            icon: <Trash className=\"w-4 h-4 mr-2\" />,\n            action: parentDatabaseName\n              ? handleDeleteTable(parentDatabaseName, node.name)\n              : () => {\n                  toast.error(\"Parent database name is undefined.\");\n                },\n          },\n        ],\n      }),\n      [parentDatabaseName, node.name, handleQueryData, handleDeleteTable, handleShowSchema]\n    );\n\n    const matchesSearch = node.name.toLowerCase().includes(searchTerm.toLowerCase());\n    const childrenMatchSearch = node.children?.some(\n      (child) =>\n        child.name.toLowerCase().includes(searchTerm.toLowerCase()) ||\n        child.children?.some((grandchild) =>\n          grandchild.name.toLowerCase().includes(searchTerm.toLowerCase())\n        )\n    );\n\n    const shouldRender = !searchTerm || matchesSearch || childrenMatchSearch;\n\n    return shouldRender ? (\n      <>\n        <ContextMenu>\n          <ContextMenuTrigger>\n            <div\n              role=\"treeitem\"\n              aria-expanded={node.children ? isOpen : undefined}\n              aria-level={level + 1}\n              tabIndex={0}\n              className={`flex items-center py-1 px-2 hover:bg-secondary hover:rounded-md cursor-pointer truncate\n              ${level > 0 ? \"ml-4\" : \"\"}`}\n              onClick={toggleOpen}\n              onKeyDown={(e) => {\n                if (e.key === \"Enter\" || e.key === \" \") {\n                  e.preventDefault();\n                  toggleOpen();\n                }\n              }}\n            >\n              <div className=\"flex-grow flex items-center\">\n                {node.children ? (\n                  isOpen ? (\n                    <ChevronDown className=\"w-4 h-4 mr-1\" />\n                  ) : (\n                    <ChevronRight className=\"w-4 h-4 mr-1\" />\n                  )\n                ) : (\n                  <div className=\"w-6 mr-1\" />\n                )}\n                {getIcon}\n                <div className=\"text-xs\">\n                  <p className=\"truncate\"> {node.name}</p>\n                </div>\n              </div>\n              <div className=\"flex items-center\">\n                {contextMenuOptions[node.type as keyof typeof contextMenuOptions].length > 0 && (\n                  <DropdownMenu>\n                    <DropdownMenuTrigger asChild>\n                      <Button size=\"icon\" variant=\"ghost\" className=\"h-6 w-6\">\n                        <MoreVertical className=\"w-4 h-4\" />\n                      </Button>\n                    </DropdownMenuTrigger>\n                    <DropdownMenuContent>\n                      {contextMenuOptions[node.type as keyof typeof contextMenuOptions].map(\n                        (option, index) => (\n                          <DropdownMenuItem key={index} onSelect={option.action}>\n                            {option.icon}\n                            {option.label}\n                          </DropdownMenuItem>\n                        )\n                      )}\n                    </DropdownMenuContent>\n                  </DropdownMenu>\n                )}\n              </div>\n            </div>\n          </ContextMenuTrigger>\n          <ContextMenuContent>\n            {contextMenuOptions[node.type as keyof typeof contextMenuOptions].map(\n              (option, index) => (\n                <ContextMenuItem key={index} onSelect={option.action}>\n                  {option.icon}\n                  {option.label}\n                </ContextMenuItem>\n              )\n            )}\n          </ContextMenuContent>\n          {(isOpen || searchTerm) && node.children && (\n            <div>\n              {node.children.length > 0 ? (\n                node.children.map((child) => (\n                  <TreeNode\n                    key={`${node.type === \"database\" ? node.name : parentDatabaseName}-${child.name}`}\n                    node={child}\n                    level={level + 1}\n                    searchTerm={searchTerm}\n                    parentDatabaseName={node.type === \"database\" ? node.name : parentDatabaseName}\n                    refreshData={refreshData}\n                  />\n                ))\n              ) : (\n                <div className=\"ml-6 pl-4 text-xs italic text-muted-foreground\">\n                  Nothing to show\n                </div>\n              )}\n            </div>\n          )}\n\n          {/* Render column stats for tables */}\n          {isOpen && node.type === \"table\" && (\n            <div>\n              {isLoadingStats ? (\n                <div className=\"ml-8 flex items-center gap-2 py-2 text-xs text-muted-foreground\">\n                  <Loader2 className=\"w-3 h-3 animate-spin\" />\n                  <span>Loading column statistics...</span>\n                </div>\n              ) : columnStats.length > 0 ? (\n                columnStats.map((stats) => <ColumnNode key={stats.column_name} stats={stats} />)\n              ) : null}\n            </div>\n          )}\n        </ContextMenu>\n      </>\n    ) : null;\n  }\n);\n\nexport default TreeNode;\n"
  },
  {
    "path": "src/components/folders/FolderBrowser.tsx",
    "content": "import React, { useEffect, useState, useCallback } from \"react\";\nimport {\n  FolderOpen,\n  FolderPlus,\n  ChevronRight,\n  ChevronDown,\n  File,\n  RefreshCw,\n  X,\n  AlertCircle,\n  FileSpreadsheet,\n  FileJson,\n  Database,\n  Import,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useDuckStore, type MountedFolderInfo } from \"@/store\";\nimport {\n  fileSystemService,\n  type FSEntry,\n  type FileEntry,\n  type FolderEntry,\n  SUPPORTED_EXTENSIONS,\n} from \"@/lib/fileSystem\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport { toast } from \"sonner\";\nimport ImportOptionsPopover, { type ImportOptions } from \"@/components/common/ImportOptionsPopover\";\n\ninterface FolderBrowserProps {\n  onFileSelect?: (folderId: string, file: FileEntry) => void;\n  onFileImport?: (folderId: string, file: FileEntry, options: ImportOptions) => void;\n  className?: string;\n}\n\n// File type icon mapping\nconst getFileIcon = (extension: string) => {\n  switch (extension.toLowerCase()) {\n    case \".csv\":\n    case \".tsv\":\n    case \".xlsx\":\n    case \".xls\":\n      return <FileSpreadsheet className=\"h-4 w-4 text-green-500\" />;\n    case \".json\":\n    case \".jsonl\":\n    case \".ndjson\":\n      return <FileJson className=\"h-4 w-4 text-yellow-500\" />;\n    case \".parquet\":\n    case \".arrow\":\n    case \".ipc\":\n      return <Database className=\"h-4 w-4 text-blue-500\" />;\n    case \".duckdb\":\n    case \".db\":\n      return <Database className=\"h-4 w-4 text-orange-500\" />;\n    default:\n      return <File className=\"h-4 w-4 text-muted-foreground\" />;\n  }\n};\n\n// Format file size\nconst formatSize = (bytes: number): string => {\n  if (bytes === 0) return \"0 B\";\n  const k = 1024;\n  const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + \" \" + sizes[i];\n};\n\n// File node component\ninterface FileNodeProps {\n  entry: FSEntry;\n  folderId: string;\n  level: number;\n  onFileSelect?: (folderId: string, file: FileEntry) => void;\n  onFileImport?: (folderId: string, file: FileEntry, options: ImportOptions) => void;\n}\n\nconst FileNode: React.FC<FileNodeProps> = ({\n  entry,\n  folderId,\n  level,\n  onFileSelect,\n  onFileImport,\n}) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [children, setChildren] = useState<FSEntry[]>([]);\n  const [isLoading, setIsLoading] = useState(false);\n\n  const loadChildren = useCallback(async () => {\n    if (entry.type !== \"folder\") return;\n\n    setIsLoading(true);\n    try {\n      const folderEntry = entry as FolderEntry;\n      const entries: FSEntry[] = [];\n\n      for await (const child of folderEntry.handle.values()) {\n        if (child.kind === \"file\") {\n          const fileHandle = child as FileSystemFileHandle;\n          const ext =\n            child.name.lastIndexOf(\".\") > 0\n              ? child.name.slice(child.name.lastIndexOf(\".\")).toLowerCase()\n              : \"\";\n\n          if (!SUPPORTED_EXTENSIONS.includes(ext)) continue;\n\n          try {\n            const file = await fileHandle.getFile();\n            entries.push({\n              name: child.name,\n              path: `${entry.path}/${child.name}`,\n              type: \"file\",\n              size: file.size,\n              lastModified: new Date(file.lastModified),\n              extension: ext,\n              handle: fileHandle,\n            });\n          } catch {\n            // Skip files we can't read\n          }\n        } else if (child.kind === \"directory\") {\n          entries.push({\n            name: child.name,\n            path: `${entry.path}/${child.name}`,\n            type: \"folder\",\n            handle: child as FileSystemDirectoryHandle,\n          });\n        }\n      }\n\n      // Sort: folders first, then files\n      entries.sort((a, b) => {\n        if (a.type !== b.type) return a.type === \"folder\" ? -1 : 1;\n        return a.name.localeCompare(b.name);\n      });\n\n      setChildren(entries);\n    } catch (error) {\n      console.error(\"Failed to load folder contents:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  }, [entry]);\n\n  const handleToggle = async () => {\n    if (entry.type === \"folder\") {\n      if (!isExpanded && children.length === 0) {\n        await loadChildren();\n      }\n      setIsExpanded(!isExpanded);\n    }\n  };\n\n  const handleFileClick = () => {\n    if (entry.type === \"file\" && onFileSelect) {\n      onFileSelect(folderId, entry as FileEntry);\n    }\n  };\n\n  const handleImport = (options: ImportOptions) => {\n    if (entry.type === \"file\" && onFileImport) {\n      onFileImport(folderId, entry as FileEntry, options);\n    }\n  };\n\n  const isFile = entry.type === \"file\";\n  const fileEntry = entry as FileEntry;\n\n  return (\n    <li>\n      <ContextMenu>\n        <ContextMenuTrigger asChild>\n          <div\n            className={cn(\n              \"group w-full flex items-center gap-1.5 py-1 px-2 text-left text-sm rounded-md\",\n              \"hover:bg-accent/50 transition-colors\",\n              \"focus-within:bg-accent cursor-pointer\"\n            )}\n            style={{ paddingLeft: `${level * 12 + 8}px` }}\n            onClick={isFile ? handleFileClick : handleToggle}\n          >\n            {entry.type === \"folder\" ? (\n              <>\n                {isLoading ? (\n                  <RefreshCw className=\"h-3.5 w-3.5 animate-spin text-muted-foreground\" />\n                ) : isExpanded ? (\n                  <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n                ) : (\n                  <ChevronRight className=\"h-3.5 w-3.5 text-muted-foreground\" />\n                )}\n                <FolderOpen className=\"h-4 w-4 text-amber-500\" />\n              </>\n            ) : (\n              <>\n                <span className=\"w-3.5\" />\n                {getFileIcon(fileEntry.extension)}\n              </>\n            )}\n            <span className=\"truncate flex-1\">{entry.name}</span>\n            {isFile && (\n              <>\n                <ImportOptionsPopover fileName={entry.name} onImport={handleImport}>\n                  <button\n                    type=\"button\"\n                    onClick={(e) => e.stopPropagation()}\n                    className={cn(\n                      \"opacity-0 group-hover:opacity-100 p-1 rounded transition-opacity\",\n                      \"hover:bg-primary/20 text-primary\"\n                    )}\n                  >\n                    <Import className=\"h-3.5 w-3.5\" />\n                  </button>\n                </ImportOptionsPopover>\n                <span className=\"text-[10px] text-muted-foreground min-w-[50px] text-right\">\n                  {formatSize(fileEntry.size)}\n                </span>\n              </>\n            )}\n          </div>\n        </ContextMenuTrigger>\n        <ContextMenuContent>\n          {isFile && (\n            <ContextMenuItem\n              onClick={() =>\n                handleImport({\n                  tableName: entry.name\n                    .replace(/\\.[^.]+$/, \"\")\n                    .replace(/[^a-zA-Z0-9_]/g, \"_\")\n                    .replace(/^[0-9]/, \"_$&\"),\n                  importMode: \"table\",\n                })\n              }\n            >\n              Quick Import as Table\n            </ContextMenuItem>\n          )}\n        </ContextMenuContent>\n      </ContextMenu>\n\n      {entry.type === \"folder\" && isExpanded && children.length > 0 && (\n        <ul>\n          {children.map((child) => (\n            <FileNode\n              key={child.path}\n              entry={child}\n              folderId={folderId}\n              level={level + 1}\n              onFileSelect={onFileSelect}\n              onFileImport={onFileImport}\n            />\n          ))}\n        </ul>\n      )}\n    </li>\n  );\n};\n\n// Mounted folder component\ninterface MountedFolderNodeProps {\n  folder: MountedFolderInfo;\n  onFileSelect?: (folderId: string, file: FileEntry) => void;\n  onFileImport?: (folderId: string, file: FileEntry, options: ImportOptions) => void;\n  onUnmount: (id: string) => void;\n  onRequestPermission: (id: string) => void;\n}\n\nconst MountedFolderNode: React.FC<MountedFolderNodeProps> = ({\n  folder,\n  onFileSelect,\n  onFileImport,\n  onUnmount,\n  onRequestPermission,\n}) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [files, setFiles] = useState<FSEntry[]>([]);\n  const [isLoading, setIsLoading] = useState(false);\n\n  const loadFiles = useCallback(async () => {\n    setIsLoading(true);\n    try {\n      const entries = await fileSystemService.listFiles(folder.id, {\n        recursive: false,\n        filterSupported: true,\n      });\n      setFiles(entries);\n    } catch (error) {\n      console.error(\"Failed to load files:\", error);\n      if (error instanceof Error && error.message === \"Permission denied\") {\n        toast.error(\"Permission required. Click to grant access.\");\n      }\n    } finally {\n      setIsLoading(false);\n    }\n  }, [folder.id]);\n\n  const handleToggle = async () => {\n    if (!folder.hasPermission) {\n      onRequestPermission(folder.id);\n      return;\n    }\n\n    if (!isExpanded && files.length === 0) {\n      await loadFiles();\n    }\n    setIsExpanded(!isExpanded);\n  };\n\n  return (\n    <li>\n      <ContextMenu>\n        <ContextMenuTrigger asChild>\n          <button\n            type=\"button\"\n            onClick={handleToggle}\n            className={cn(\n              \"w-full flex items-center gap-1.5 py-1.5 px-2 text-left text-sm rounded-md\",\n              \"hover:bg-accent/50 transition-colors\",\n              \"focus:outline-none focus:bg-accent\"\n            )}\n          >\n            {isLoading ? (\n              <RefreshCw className=\"h-3.5 w-3.5 animate-spin text-muted-foreground\" />\n            ) : isExpanded ? (\n              <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n            ) : (\n              <ChevronRight className=\"h-3.5 w-3.5 text-muted-foreground\" />\n            )}\n            <FolderOpen className=\"h-4 w-4 text-amber-500\" />\n            <span className=\"truncate flex-1 font-medium\">{folder.name}</span>\n            {!folder.hasPermission && (\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <AlertCircle className=\"h-3.5 w-3.5 text-amber-500\" />\n                </TooltipTrigger>\n                <TooltipContent>Click to grant permission</TooltipContent>\n              </Tooltip>\n            )}\n          </button>\n        </ContextMenuTrigger>\n        <ContextMenuContent>\n          <ContextMenuItem onClick={loadFiles}>\n            <RefreshCw className=\"h-4 w-4 mr-2\" />\n            Refresh\n          </ContextMenuItem>\n          <ContextMenuItem onClick={() => onUnmount(folder.id)} className=\"text-destructive\">\n            <X className=\"h-4 w-4 mr-2\" />\n            Unmount\n          </ContextMenuItem>\n        </ContextMenuContent>\n      </ContextMenu>\n\n      {isExpanded && files.length > 0 && (\n        <ul className=\"ml-2\">\n          {files.map((entry) => (\n            <FileNode\n              key={entry.path}\n              entry={entry}\n              folderId={folder.id}\n              level={1}\n              onFileSelect={onFileSelect}\n              onFileImport={onFileImport}\n            />\n          ))}\n        </ul>\n      )}\n\n      {isExpanded && files.length === 0 && !isLoading && (\n        <p className=\"text-xs text-muted-foreground ml-8 py-1\">No supported files found</p>\n      )}\n    </li>\n  );\n};\n\nconst FolderBrowser: React.FC<FolderBrowserProps> = ({ onFileSelect, onFileImport, className }) => {\n  const mountedFolders = useDuckStore((s) => s.mountedFolders);\n  const isFileSystemSupported = useDuckStore((s) => s.isFileSystemSupported);\n  const initFileSystem = useDuckStore((s) => s.initFileSystem);\n  const mountFolder = useDuckStore((s) => s.mountFolder);\n  const unmountFolder = useDuckStore((s) => s.unmountFolder);\n  const refreshFolderPermissions = useDuckStore((s) => s.refreshFolderPermissions);\n\n  const [isInitialized, setIsInitialized] = useState(false);\n\n  // Initialize file system on mount\n  useEffect(() => {\n    const init = async () => {\n      await initFileSystem();\n      setIsInitialized(true);\n    };\n    init();\n  }, [initFileSystem]);\n\n  // Refresh permissions on visibility change (tab focus)\n  useEffect(() => {\n    const handleVisibilityChange = () => {\n      if (document.visibilityState === \"visible\" && isInitialized) {\n        refreshFolderPermissions();\n      }\n    };\n\n    document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n    return () => document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n  }, [isInitialized, refreshFolderPermissions]);\n\n  const handleRequestPermission = async (folderId: string) => {\n    try {\n      const granted = await fileSystemService.requestPermission(folderId);\n      if (granted) {\n        await refreshFolderPermissions();\n        toast.success(\"Permission granted\");\n      } else {\n        toast.error(\"Permission denied\");\n      }\n    } catch {\n      toast.error(\"Failed to request permission\");\n    }\n  };\n\n  const handleFileImport = async (folderId: string, file: FileEntry, options: ImportOptions) => {\n    if (onFileImport) {\n      onFileImport(folderId, file, options);\n    }\n  };\n\n  if (!isFileSystemSupported) {\n    return (\n      <div className={cn(\"p-3 text-center\", className)}>\n        <p className=\"text-xs text-muted-foreground\">\n          File System Access not supported.\n          <br />\n          Use Chrome or Edge 86+.\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <TooltipProvider>\n      <div className={cn(\"space-y-2\", className)}>\n        {/* Header with Add Folder button */}\n        <div className=\"flex items-center justify-between px-2 py-1\">\n          <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n            Files\n          </span>\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6\" onClick={mountFolder}>\n                <FolderPlus className=\"h-4 w-4\" />\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent>Add folder</TooltipContent>\n          </Tooltip>\n        </div>\n\n        {/* Mounted folders list */}\n        {mountedFolders.length > 0 ? (\n          <ul className=\"space-y-0.5\">\n            {mountedFolders.map((folder) => (\n              <MountedFolderNode\n                key={folder.id}\n                folder={folder}\n                onFileSelect={onFileSelect}\n                onFileImport={handleFileImport}\n                onUnmount={unmountFolder}\n                onRequestPermission={handleRequestPermission}\n              />\n            ))}\n          </ul>\n        ) : (\n          <div className=\"px-2 py-4 text-center\">\n            <FolderOpen className=\"h-8 w-8 mx-auto mb-2 text-muted-foreground/50\" />\n            <p className=\"text-xs text-muted-foreground mb-2\">No folders mounted</p>\n            <Button variant=\"outline\" size=\"sm\" className=\"gap-1.5\" onClick={mountFolder}>\n              <FolderPlus className=\"h-3.5 w-3.5\" />\n              Add Folder\n            </Button>\n          </div>\n        )}\n      </div>\n    </TooltipProvider>\n  );\n};\n\nexport default FolderBrowser;\n"
  },
  {
    "path": "src/components/layout/ConnectionSwitcher.tsx",
    "content": "import * as React from \"react\";\nimport { ChevronDown, Circle, Loader2, Cable } from \"lucide-react\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\nimport { useDuckStore } from \"@/store\";\n\ninterface ConnectionSwitcherProps {\n  className?: string;\n}\n\nexport default function ConnectionSwitcher({ className }: ConnectionSwitcherProps) {\n  const connectionList = useDuckStore((s) => s.connectionList);\n  const currentConnection = useDuckStore((s) => s.currentConnection);\n  const setCurrentConnection = useDuckStore((s) => s.setCurrentConnection);\n  const isLoading = useDuckStore((s) => s.isLoading);\n  const fetchDatabasesAndTablesInfo = useDuckStore((s) => s.fetchDatabasesAndTablesInfo);\n  const tabs = useDuckStore((s) => s.tabs);\n  const createTab = useDuckStore((s) => s.createTab);\n  const setActiveTab = useDuckStore((s) => s.setActiveTab);\n  const [isOpen, setIsOpen] = React.useState(false);\n\n  // Helper to open or focus Connections tab\n  const openConnectionsTab = () => {\n    const existing = tabs.find((t) => t.type === \"connections\");\n    if (existing) {\n      setActiveTab(existing.id);\n    } else {\n      createTab(\"connections\", \"\", \"Connections\");\n    }\n  };\n\n  const activeConnection = currentConnection || connectionList.connections[0];\n\n  const handleConnectionChange = async (connectionId: string) => {\n    try {\n      await setCurrentConnection(connectionId);\n      await fetchDatabasesAndTablesInfo();\n      setIsOpen(false);\n    } catch (error) {\n      console.error(\"Failed to switch connection:\", error);\n    }\n  };\n\n  const getStatusColor = (scope: string) => {\n    switch (scope) {\n      case \"WASM\":\n        return \"bg-green-500\";\n      case \"External\":\n        return \"bg-blue-500\";\n      case \"OPFS\":\n        return \"bg-purple-500\";\n      default:\n        return \"bg-gray-500\";\n    }\n  };\n\n  return (\n    <div className={cn(className)}>\n      <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>\n        <DropdownMenuTrigger asChild>\n          <Button\n            variant=\"outline\"\n            className={cn(\n              \"flex items-center gap-2 h-9 px-3\",\n              \"hover:bg-accent transition-colors\",\n              isLoading && \"opacity-70 cursor-not-allowed\"\n            )}\n            disabled={isLoading}\n          >\n            {isLoading ? (\n              <Loader2 className=\"h-3 w-3 animate-spin\" />\n            ) : (\n              <Circle\n                className={cn(\"h-2 w-2\", getStatusColor(activeConnection?.scope || \"WASM\"))}\n                fill=\"currentColor\"\n              />\n            )}\n            <span className=\"font-medium text-sm truncate max-w-[120px]\">\n              {activeConnection?.name}\n            </span>\n            <Badge variant=\"secondary\" className=\"text-[10px] px-1.5 py-0\">\n              {activeConnection?.scope}\n            </Badge>\n            <ChevronDown className=\"h-3 w-3 opacity-50\" />\n          </Button>\n        </DropdownMenuTrigger>\n\n        <DropdownMenuContent className=\"w-[240px]\" align=\"start\" sideOffset={8}>\n          <DropdownMenuLabel className=\"text-xs font-normal text-muted-foreground\">\n            Switch Connection\n          </DropdownMenuLabel>\n\n          {connectionList.connections.map((connection) => (\n            <DropdownMenuItem\n              key={connection.id}\n              onClick={() => handleConnectionChange(connection.id)}\n              className={cn(\n                \"flex items-center gap-2 cursor-pointer\",\n                activeConnection?.id === connection.id && \"bg-accent\"\n              )}\n            >\n              <Circle\n                className={cn(\"h-2 w-2\", getStatusColor(connection.scope))}\n                fill=\"currentColor\"\n              />\n              <span className=\"flex-1 font-medium\">{connection.name}</span>\n              <Badge variant=\"secondary\" className=\"text-[10px] px-1.5 py-0\">\n                {connection.scope}\n              </Badge>\n            </DropdownMenuItem>\n          ))}\n\n          <DropdownMenuSeparator />\n\n          <DropdownMenuItem\n            className=\"cursor-pointer\"\n            onClick={() => {\n              openConnectionsTab();\n              setIsOpen(false);\n            }}\n          >\n            <Cable className=\"h-4 w-4 mr-2\" />\n            <span>Manage Connections</span>\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/MobileNavDrawer.tsx",
    "content": "import { useState } from \"react\";\nimport { Menu, Github, BookText, Cable, Sun, Moon, Brain } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from \"@/components/ui/sheet\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport { useDuckStore, type EditorTabType } from \"@/store\";\nimport ConnectionSwitcher from \"./ConnectionSwitcher\";\n\nexport const MobileNavDrawer = () => {\n  const [open, setOpen] = useState(false);\n  const { theme, setTheme } = useTheme();\n  const tabs = useDuckStore((s) => s.tabs);\n  const setActiveTab = useDuckStore((s) => s.setActiveTab);\n  const createTab = useDuckStore((s) => s.createTab);\n\n  const toggleTheme = () => {\n    setTheme(theme === \"dark\" ? \"light\" : \"dark\");\n  };\n\n  const openOrFocusTab = (type: EditorTabType, title: string) => {\n    const existing = tabs.find((t) => t.type === type);\n    if (existing) {\n      setActiveTab(existing.id);\n    } else {\n      createTab(type, \"\", title);\n    }\n    setOpen(false);\n  };\n\n  const handleExternalLink = (url: string) => {\n    window.open(url, \"_blank\");\n    setOpen(false);\n  };\n\n  return (\n    <Sheet open={open} onOpenChange={setOpen}>\n      <SheetTrigger asChild>\n        <Button variant=\"ghost\" size=\"icon\" className=\"md:hidden\" aria-label=\"Open menu\">\n          <Menu className=\"h-5 w-5\" />\n        </Button>\n      </SheetTrigger>\n      <SheetContent side=\"left\" className=\"w-[300px] sm:w-[350px]\">\n        <SheetHeader>\n          <SheetTitle className=\"text-left flex items-center gap-2\">\n            <img src=\"/logo.png\" alt=\"Duck-UI\" className=\"h-8 w-8\" />\n            Duck-UI\n          </SheetTitle>\n        </SheetHeader>\n\n        <div className=\"mt-6 space-y-4\">\n          {/* Connection Switcher */}\n          <div>\n            <p className=\"text-sm font-medium mb-2 text-muted-foreground\">Connection</p>\n            <ConnectionSwitcher />\n          </div>\n\n          <Separator />\n\n          {/* Navigation */}\n          <div className=\"space-y-2\">\n            <p className=\"text-sm font-medium mb-2 text-muted-foreground\">Navigate</p>\n            <Button\n              variant=\"ghost\"\n              className=\"w-full justify-start\"\n              onClick={() => openOrFocusTab(\"home\", \"Home\")}\n            >\n              Home\n            </Button>\n            <Button\n              variant=\"ghost\"\n              className=\"w-full justify-start\"\n              onClick={() => openOrFocusTab(\"connections\", \"Connections\")}\n            >\n              <Cable className=\"mr-2 h-4 w-4\" />\n              Manage Connections\n            </Button>\n            <Button\n              variant=\"ghost\"\n              className=\"w-full justify-start\"\n              onClick={() => openOrFocusTab(\"brain\", \"Duck Brain\")}\n            >\n              <Brain className=\"mr-2 h-4 w-4\" />\n              Duck Brain Settings\n            </Button>\n          </div>\n\n          <Separator />\n\n          {/* External Links */}\n          <div className=\"space-y-2\">\n            <p className=\"text-sm font-medium mb-2 text-muted-foreground\">Resources</p>\n            <Button\n              variant=\"ghost\"\n              className=\"w-full justify-start\"\n              onClick={() =>\n                handleExternalLink(\n                  \"https://github.com/caioricciuti/duck-ui?utm_source=duck-ui&utm_medium=mobile-nav\"\n                )\n              }\n            >\n              <Github className=\"mr-2 h-4 w-4\" />\n              GitHub\n            </Button>\n            <Button\n              variant=\"ghost\"\n              className=\"w-full justify-start\"\n              onClick={() =>\n                handleExternalLink(\"https://duckui.com?utm_source=duck-ui&utm_medium=mobile-nav\")\n              }\n            >\n              <BookText className=\"mr-2 h-4 w-4\" />\n              Documentation\n            </Button>\n          </div>\n\n          <Separator />\n\n          {/* Theme Toggle */}\n          <div className=\"space-y-2\">\n            <p className=\"text-sm font-medium mb-2 text-muted-foreground\">Appearance</p>\n            <Button variant=\"outline\" className=\"w-full justify-start\" onClick={toggleTheme}>\n              {theme === \"dark\" ? (\n                <>\n                  <Sun className=\"mr-2 h-4 w-4\" />\n                  Switch to Light Mode\n                </>\n              ) : (\n                <>\n                  <Moon className=\"mr-2 h-4 w-4\" />\n                  Switch to Dark Mode\n                </>\n              )}\n            </Button>\n          </div>\n        </div>\n      </SheetContent>\n    </Sheet>\n  );\n};\n\nexport default MobileNavDrawer;\n"
  },
  {
    "path": "src/components/layout/Sidebar.tsx",
    "content": "/**\n * Sidebar Component\n * Minimalist icon-only sidebar with tooltips\n */\n\nimport { useState } from \"react\";\nimport {\n  Home,\n  Database,\n  Cable,\n  Brain,\n  Moon,\n  Sun,\n  HelpCircle,\n  Github,\n  BookOpen,\n  Search,\n  History,\n  Circle,\n  Settings,\n  Bookmark,\n  ChevronRight,\n} from \"lucide-react\";\nimport { useDuckStore, type EditorTabType } from \"@/store\";\nimport { setSetting } from \"@/services/persistence/repositories/settingsRepository\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport { Separator } from \"@/components/ui/separator\";\nimport QueryHistory from \"../workspace/QueryHistory\";\nimport SavedQueriesPanel from \"@/components/saved-queries/SavedQueriesPanel\";\nimport PasswordDialog from \"@/components/profile/PasswordDialog\";\nimport ProfileAvatar from \"@/components/profile/ProfileAvatar\";\n\ninterface SidebarProps {\n  isExplorerOpen: boolean;\n  onToggleExplorer: () => void;\n}\n\nexport default function Sidebar({ isExplorerOpen, onToggleExplorer }: SidebarProps) {\n  const { theme, setTheme } = useTheme();\n  const tabs = useDuckStore((s) => s.tabs);\n  const activeTabId = useDuckStore((s) => s.activeTabId);\n  const createTab = useDuckStore((s) => s.createTab);\n  const setActiveTab = useDuckStore((s) => s.setActiveTab);\n  const currentConnection = useDuckStore((s) => s.currentConnection);\n  const currentProfile = useDuckStore((s) => s.currentProfile);\n  const profiles = useDuckStore((s) => s.profiles);\n  const switchProfile = useDuckStore((s) => s.switchProfile);\n  const [historyOpen, setHistoryOpen] = useState(false);\n  const [savedQueriesOpen, setSavedQueriesOpen] = useState(false);\n  const [switchTarget, setSwitchTarget] = useState<(typeof profiles)[0] | null>(null);\n\n  // Get connection status color\n  const getConnectionColor = (scope?: string) => {\n    switch (scope) {\n      case \"WASM\":\n        return \"text-green-500\";\n      case \"External\":\n        return \"text-blue-500\";\n      case \"OPFS\":\n        return \"text-purple-500\";\n      default:\n        return \"text-gray-500\";\n    }\n  };\n\n  // Helper to open or focus a singleton tab\n  const openOrFocusTab = (type: EditorTabType, title: string) => {\n    const existing = tabs.find((t) => t.type === type);\n    if (existing) {\n      setActiveTab(existing.id);\n    } else {\n      createTab(type, \"\", title);\n    }\n  };\n\n  // Check if a tab type is active\n  const isTabActive = (type: EditorTabType) => {\n    const activeTab = tabs.find((t) => t.id === activeTabId);\n    return activeTab?.type === type;\n  };\n\n  // Mutual exclusion for right panels\n  const openHistory = () => {\n    setSavedQueriesOpen(false);\n    setHistoryOpen(!historyOpen);\n  };\n\n  const openSavedQueries = () => {\n    setHistoryOpen(false);\n    setSavedQueriesOpen(!savedQueriesOpen);\n  };\n\n  // Profile switching\n  const handleSwitchProfile = (profile: (typeof profiles)[0]) => {\n    if (profile.hasPassword) {\n      setSwitchTarget(profile);\n    } else {\n      switchProfile(profile.id);\n    }\n  };\n\n  const otherProfiles = profiles.filter((p) => p.id !== currentProfile?.id);\n\n  return (\n    <>\n      <nav\n        aria-label=\"Main navigation\"\n        className=\"flex flex-col h-full w-16 border-r bg-background shrink-0\"\n      >\n        {/* Profile Avatar */}\n        <div className=\"flex items-center justify-center w-16 h-10 border-b\">\n          <DropdownMenu>\n            <TooltipProvider>\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <DropdownMenuTrigger asChild>\n                    <button className=\"p-1.5 rounded-md hover:bg-muted transition-colors\">\n                      <ProfileAvatar\n                        avatarEmoji={currentProfile?.avatarEmoji || \"logo\"}\n                        size=\"md\"\n                      />\n                    </button>\n                  </DropdownMenuTrigger>\n                </TooltipTrigger>\n                <TooltipContent side=\"right\">{currentProfile?.name || \"Duck-UI\"}</TooltipContent>\n              </Tooltip>\n            </TooltipProvider>\n            <DropdownMenuContent side=\"right\" align=\"start\">\n              <DropdownMenuLabel className=\"flex items-center gap-2\">\n                <ProfileAvatar avatarEmoji={currentProfile?.avatarEmoji || \"logo\"} size=\"md\" />\n                <div>\n                  <div className=\"font-medium\">{currentProfile?.name}</div>\n                  <div className=\"text-xs text-muted-foreground font-normal\">Active</div>\n                </div>\n              </DropdownMenuLabel>\n              {otherProfiles.length > 0 && (\n                <>\n                  <DropdownMenuSeparator />\n                  <DropdownMenuLabel className=\"text-xs text-muted-foreground font-normal\">\n                    Switch Profile\n                  </DropdownMenuLabel>\n                  {otherProfiles.map((p) => (\n                    <DropdownMenuItem key={p.id} onClick={() => handleSwitchProfile(p)}>\n                      <ProfileAvatar avatarEmoji={p.avatarEmoji} size=\"sm\" className=\"mr-2\" />\n                      {p.name}\n                      <ChevronRight className=\"ml-auto h-3 w-3\" />\n                    </DropdownMenuItem>\n                  ))}\n                </>\n              )}\n              <DropdownMenuSeparator />\n              <DropdownMenuItem onClick={() => openOrFocusTab(\"settings\", \"Settings\")}>\n                <Settings className=\"h-4 w-4 mr-2\" />\n                Settings\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n        </div>\n\n        {/* Main Navigation */}\n        <div className=\"flex-1 flex flex-col py-2 gap-1\">\n          {/* Home */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={isTabActive(\"home\") ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={() => openOrFocusTab(\"home\", \"Home\")}\n                  aria-label=\"Home\"\n                >\n                  <Home className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">Home</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Explorer Toggle */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={isExplorerOpen ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={onToggleExplorer}\n                  aria-label={isExplorerOpen ? \"Hide Explorer\" : \"Show Explorer\"}\n                >\n                  <Database className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">\n                {isExplorerOpen ? \"Hide Explorer\" : \"Show Explorer\"}\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          <Separator className=\"my-2 mx-2\" />\n\n          {/* Connections */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={isTabActive(\"connections\") ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={() => openOrFocusTab(\"connections\", \"Connections\")}\n                  aria-label=\"Connections\"\n                >\n                  <Cable className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">Connections</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Brain */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={isTabActive(\"brain\") ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={() => openOrFocusTab(\"brain\", \"Duck Brain\")}\n                  aria-label=\"Duck Brain\"\n                >\n                  <Brain className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">Duck Brain</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          <Separator className=\"my-2 mx-2\" />\n\n          {/* Search */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={() => {\n                    document.dispatchEvent(\n                      new KeyboardEvent(\"keydown\", { key: \"k\", metaKey: true })\n                    );\n                  }}\n                  aria-label=\"Search\"\n                >\n                  <Search className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">\n                Search ({navigator.platform?.includes(\"Mac\") ? \"⌘\" : \"Ctrl+\"}K)\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Saved Queries */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={savedQueriesOpen ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={openSavedQueries}\n                  aria-label=\"Saved Queries\"\n                >\n                  <Bookmark className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">Saved Queries</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Query History */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={historyOpen ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={openHistory}\n                  aria-label=\"Query History\"\n                >\n                  <History className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">Query History</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n        </div>\n\n        {/* Bottom Actions */}\n        <div className=\"flex flex-col py-2 gap-1 border-t\">\n          {/* Connection Status Pill */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9 relative\"\n                  onClick={() => openOrFocusTab(\"connections\", \"Connections\")}\n                  aria-label=\"Connection status\"\n                >\n                  <Circle\n                    className={`h-3 w-3 ${getConnectionColor(currentConnection?.scope)}`}\n                    fill=\"currentColor\"\n                  />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">\n                <div className=\"text-xs\">\n                  <div className=\"font-medium\">{currentConnection?.name || \"No connection\"}</div>\n                  <div className=\"text-muted-foreground\">\n                    {currentConnection?.scope || \"Click to manage\"}\n                  </div>\n                </div>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Settings */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant={isTabActive(\"settings\") ? \"secondary\" : \"ghost\"}\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={() => openOrFocusTab(\"settings\", \"Settings\")}\n                  aria-label=\"Settings\"\n                >\n                  <Settings className=\"h-4 w-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">Settings</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Theme Toggle */}\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"mx-auto h-9 w-9\"\n                  onClick={() => {\n                    const newTheme = theme === \"dark\" ? \"light\" : \"dark\";\n                    setTheme(newTheme);\n                    const profileId = useDuckStore.getState().currentProfileId;\n                    if (profileId) {\n                      setSetting(profileId, \"theme\", \"mode\", JSON.stringify(newTheme)).catch(\n                        () => {}\n                      );\n                    }\n                  }}\n                  aria-label={theme === \"dark\" ? \"Switch to light mode\" : \"Switch to dark mode\"}\n                >\n                  {theme === \"dark\" ? <Sun className=\"h-4 w-4\" /> : <Moon className=\"h-4 w-4\" />}\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\">\n                {theme === \"dark\" ? \"Light Mode\" : \"Dark Mode\"}\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n\n          {/* Help Dropdown */}\n          <DropdownMenu>\n            <TooltipProvider>\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <DropdownMenuTrigger asChild>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      className=\"mx-auto h-9 w-9\"\n                      aria-label=\"Help\"\n                    >\n                      <HelpCircle className=\"h-4 w-4\" />\n                    </Button>\n                  </DropdownMenuTrigger>\n                </TooltipTrigger>\n                <TooltipContent side=\"right\">Help</TooltipContent>\n              </Tooltip>\n            </TooltipProvider>\n            <DropdownMenuContent side=\"right\" align=\"end\">\n              <DropdownMenuItem\n                onClick={() => window.open(\"https://github.com/caioricciuti/duck-ui\", \"_blank\")}\n              >\n                <Github className=\"h-4 w-4 mr-2\" />\n                GitHub\n              </DropdownMenuItem>\n              <DropdownMenuItem onClick={() => window.open(\"https://duckui.com\", \"_blank\")}>\n                <BookOpen className=\"h-4 w-4 mr-2\" />\n                Documentation\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n        </div>\n      </nav>\n\n      {/* Query History Panel */}\n      {historyOpen && (\n        <div className=\"fixed right-0 top-0 h-full w-96 border-l bg-background z-40 shadow-lg\">\n          <div className=\"flex items-center justify-between px-4 py-2 border-b\">\n            <span className=\"text-sm font-medium\">Query History</span>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"h-6 w-6\"\n              onClick={() => setHistoryOpen(false)}\n            >\n              <span className=\"sr-only\">Close</span>×\n            </Button>\n          </div>\n          <div className=\"h-[calc(100%-41px)] overflow-auto\">\n            <QueryHistory isExpanded={true} mode=\"inline\" />\n          </div>\n        </div>\n      )}\n\n      {/* Saved Queries Panel */}\n      {savedQueriesOpen && (\n        <div className=\"fixed right-0 top-0 h-full w-96 border-l bg-background z-40 shadow-lg\">\n          <SavedQueriesPanel onClose={() => setSavedQueriesOpen(false)} />\n        </div>\n      )}\n\n      {/* Password Dialog for Profile Switching */}\n      {switchTarget && (\n        <PasswordDialog\n          open={!!switchTarget}\n          onOpenChange={(open) => {\n            if (!open) setSwitchTarget(null);\n          }}\n          profile={switchTarget}\n          onSubmit={async (password) => {\n            await switchProfile(switchTarget.id, password);\n            setSwitchTarget(null);\n          }}\n        />\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/notebook/MarkdownRenderer.tsx",
    "content": "import { useMemo } from \"react\";\nimport { marked } from \"marked\";\nimport DOMPurify from \"dompurify\";\n\ninterface MarkdownRendererProps {\n  content: string;\n  className?: string;\n}\n\nmarked.setOptions({\n  breaks: true,\n  gfm: true,\n});\n\nexport function MarkdownRenderer({ content, className }: MarkdownRendererProps) {\n  const html = useMemo(() => {\n    if (!content.trim()) return \"\";\n    const raw = marked.parse(content, { async: false }) as string;\n    return DOMPurify.sanitize(raw);\n  }, [content]);\n\n  if (!content.trim()) {\n    return (\n      <p className=\"text-sm text-muted-foreground italic px-1\">\n        Empty markdown cell. Click to edit.\n      </p>\n    );\n  }\n\n  return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />;\n}\n"
  },
  {
    "path": "src/components/notebook/NotebookCell.tsx",
    "content": "import React, { useRef, useEffect, useState, useCallback, useMemo } from \"react\";\nimport {\n  Play,\n  Loader2,\n  Trash2,\n  ChevronUp,\n  ChevronDown,\n  Code,\n  Type,\n  ChevronRight,\n  BarChart3,\n  Table,\n  Plus,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\nimport { useDuckStore } from \"@/store\";\nimport type { NotebookCell as NotebookCellType, QueryResult, ChartConfig } from \"@/store/types\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport {\n  createCellEditor,\n  useMonacoConfig,\n  type EditorInstance,\n} from \"@/components/editor/monacoConfig\";\nimport { MarkdownRenderer } from \"./MarkdownRenderer\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport DuckUiTable from \"@/components/table/DuckUItable\";\nimport ChartVisualizationPro from \"@/components/charts/ChartVisualizationPro\";\nimport { ErrorBoundary, type FallbackProps } from \"react-error-boundary\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nconst CellErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (\n  <div className=\"p-3 text-center\">\n    <p className=\"text-xs text-destructive mb-2\">\n      {error instanceof Error ? error.message : \"Render error\"}\n    </p>\n    <button onClick={resetErrorBoundary} className=\"text-xs px-2 py-1 bg-muted rounded\">\n      Retry\n    </button>\n  </div>\n);\n\ninterface NotebookCellProps {\n  cell: NotebookCellType;\n  tabId: string;\n  cellIndex: number;\n  totalCells: number;\n  isRunning: boolean;\n  onRun: (cellId: string) => void;\n  onAddCell: (afterCellId: string, type: \"sql\" | \"markdown\") => void;\n}\n\nexport function NotebookCellComponent({\n  cell,\n  tabId,\n  cellIndex,\n  totalCells,\n  isRunning,\n  onRun,\n  onAddCell,\n}: NotebookCellProps) {\n  const [isEditing, setIsEditing] = useState(cell.type === \"sql\" || !cell.content);\n  const [isFocused, setIsFocused] = useState(false);\n  const editorRef = useRef<HTMLDivElement>(null);\n  const editorInstanceRef = useRef<EditorInstance | null>(null);\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\n  const { theme } = useTheme();\n  const monacoConfig = useMonacoConfig(theme);\n\n  const updateCellContent = useDuckStore((s) => s.updateNotebookCellContent);\n  const updateCellChartConfig = useDuckStore((s) => s.updateNotebookCellChartConfig);\n  const removeCell = useDuckStore((s) => s.removeNotebookCell);\n  const moveCell = useDuckStore((s) => s.moveNotebookCell);\n  const toggleCollapsed = useDuckStore((s) => s.toggleNotebookCellCollapsed);\n  const toggleCellType = useDuckStore((s) => s.toggleNotebookCellType);\n\n  const hasResult =\n    cell.type === \"sql\" && cell.result && !cell.result.error && cell.result.data.length > 0;\n  const hasError = cell.type === \"sql\" && cell.result?.error;\n\n  // Status indicator\n  const statusColor = useMemo(() => {\n    if (isRunning) return \"bg-blue-500 animate-pulse\";\n    if (hasError) return \"bg-red-500\";\n    if (hasResult) return \"bg-green-500\";\n    return \"bg-muted-foreground/30\";\n  }, [isRunning, hasError, hasResult]);\n\n  // Stable callbacks for the cell editor\n  const executeCallback = useCallback(async () => {\n    onRun(cell.id);\n  }, [onRun, cell.id]);\n\n  const contentChangeCallback = useCallback(\n    (value: string) => {\n      updateCellContent(tabId, cell.id, value);\n    },\n    [tabId, cell.id, updateCellContent]\n  );\n\n  // SQL cell: Monaco editor\n  useEffect(() => {\n    if (cell.type !== \"sql\" || !editorRef.current) return;\n\n    const compactConfig = {\n      ...monacoConfig,\n      lineNumbers: \"on\" as const,\n      minimap: { enabled: false },\n      scrollBeyondLastLine: false,\n      padding: { top: 8 },\n      fontSize: 13,\n    };\n\n    editorInstanceRef.current = createCellEditor(\n      editorRef.current,\n      compactConfig,\n      cell.content,\n      executeCallback,\n      contentChangeCallback\n    );\n\n    // Auto-resize editor height based on content\n    const editor = editorInstanceRef.current.editor;\n    const updateHeight = () => {\n      const lineCount = editor.getModel()?.getLineCount() ?? 1;\n      const lineHeight = 19;\n      const minHeight = 60;\n      const maxHeight = 400;\n      const height = Math.min(Math.max(lineCount * lineHeight + 16, minHeight), maxHeight);\n      if (editorRef.current) {\n        editorRef.current.style.height = `${height}px`;\n        editor.layout();\n      }\n    };\n\n    const contentDisposable = editor.onDidChangeModelContent(updateHeight);\n    updateHeight();\n\n    return () => {\n      contentDisposable.dispose();\n      if (editorInstanceRef.current) {\n        editorInstanceRef.current.dispose();\n        editorInstanceRef.current = null;\n      }\n    };\n  }, [cell.type, cell.id, monacoConfig, executeCallback, contentChangeCallback]);\n\n  // Sync content if changed externally\n  useEffect(() => {\n    if (cell.type !== \"sql\") return;\n    const editor = editorInstanceRef.current?.editor;\n    if (editor && cell.content !== editor.getValue()) {\n      const position = editor.getPosition();\n      editor.setValue(cell.content);\n      if (position) editor.setPosition(position);\n    }\n  }, [cell.content, cell.type]);\n\n  // Markdown cell: auto-resize textarea\n  const handleMarkdownChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    updateCellContent(tabId, cell.id, e.target.value);\n    // Auto-resize\n    if (textareaRef.current) {\n      textareaRef.current.style.height = \"auto\";\n      textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;\n    }\n  };\n\n  const handleMarkdownKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if (e.key === \"Escape\") {\n      setIsEditing(false);\n    }\n  };\n\n  // Auto-focus textarea on edit\n  useEffect(() => {\n    if (cell.type === \"markdown\" && isEditing && textareaRef.current) {\n      textareaRef.current.focus();\n      // Auto-resize on mount\n      textareaRef.current.style.height = \"auto\";\n      textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;\n    }\n  }, [isEditing, cell.type]);\n\n  const handleChartConfigChange = useCallback(\n    (config: ChartConfig | undefined) => {\n      updateCellChartConfig(tabId, cell.id, config);\n    },\n    [updateCellChartConfig, tabId, cell.id]\n  );\n\n  return (\n    <div\n      className={cn(\n        \"group relative border rounded-lg transition-all\",\n        isFocused ? \"border-primary/50 shadow-sm\" : \"border-border hover:border-muted-foreground/30\"\n      )}\n      onFocus={() => setIsFocused(true)}\n      onBlur={() => setIsFocused(false)}\n    >\n      {/* Cell header */}\n      <div className=\"flex items-center gap-1 px-2 py-1 bg-muted/30 border-b rounded-t-lg\">\n        {/* Status dot */}\n        <div className={cn(\"w-2 h-2 rounded-full flex-shrink-0\", statusColor)} />\n\n        {/* Cell type badge */}\n        <Badge\n          variant=\"outline\"\n          className=\"text-[10px] px-1.5 py-0 h-5 cursor-pointer hover:bg-muted\"\n          onClick={() => toggleCellType(tabId, cell.id)}\n        >\n          {cell.type === \"sql\" ? (\n            <>\n              <Code className=\"h-3 w-3 mr-1\" />\n              SQL\n            </>\n          ) : (\n            <>\n              <Type className=\"h-3 w-3 mr-1\" />\n              MD\n            </>\n          )}\n        </Badge>\n\n        <span className=\"text-[10px] text-muted-foreground ml-1\">[{cellIndex + 1}]</span>\n\n        {/* Spacer */}\n        <div className=\"flex-1\" />\n\n        {/* Cell actions */}\n        <div className=\"flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity\">\n          {cell.type === \"sql\" && (\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"h-6 w-6\"\n              onClick={() => onRun(cell.id)}\n              disabled={isRunning}\n            >\n              {isRunning ? (\n                <Loader2 className=\"h-3 w-3 animate-spin\" />\n              ) : (\n                <Play className=\"h-3 w-3\" />\n              )}\n            </Button>\n          )}\n\n          {cell.type === \"sql\" && hasResult && (\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"h-6 w-6\"\n              onClick={() => toggleCollapsed(tabId, cell.id)}\n            >\n              <ChevronRight\n                className={cn(\"h-3 w-3 transition-transform\", !cell.collapsed && \"rotate-90\")}\n              />\n            </Button>\n          )}\n\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-6 w-6\"\n            onClick={() => moveCell(tabId, cell.id, \"up\")}\n            disabled={cellIndex === 0}\n          >\n            <ChevronUp className=\"h-3 w-3\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-6 w-6\"\n            onClick={() => moveCell(tabId, cell.id, \"down\")}\n            disabled={cellIndex === totalCells - 1}\n          >\n            <ChevronDown className=\"h-3 w-3\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-6 w-6 text-destructive hover:text-destructive\"\n            onClick={() => removeCell(tabId, cell.id)}\n            disabled={totalCells <= 1}\n          >\n            <Trash2 className=\"h-3 w-3\" />\n          </Button>\n        </div>\n      </div>\n\n      {/* Cell body */}\n      <div className=\"min-h-[40px]\">\n        {cell.type === \"sql\" ? (\n          <div ref={editorRef} className=\"w-full\" style={{ minHeight: 60 }} />\n        ) : isEditing ? (\n          <textarea\n            ref={textareaRef}\n            value={cell.content}\n            onChange={handleMarkdownChange}\n            onKeyDown={handleMarkdownKeyDown}\n            onBlur={() => cell.content.trim() && setIsEditing(false)}\n            placeholder=\"Write markdown here...\"\n            className=\"w-full p-3 bg-transparent resize-none outline-none text-sm font-mono min-h-[60px]\"\n          />\n        ) : (\n          <div className=\"p-3 cursor-pointer\" onClick={() => setIsEditing(true)}>\n            <MarkdownRenderer\n              content={cell.content}\n              className=\"prose prose-sm dark:prose-invert max-w-none\"\n            />\n          </div>\n        )}\n      </div>\n\n      {/* SQL cell results */}\n      {cell.type === \"sql\" && cell.result && !cell.collapsed && (\n        <div className=\"border-t\">\n          {cell.result.error ? (\n            <div className=\"px-3 py-2 text-sm text-destructive bg-destructive/5\">\n              {cell.result.error}\n            </div>\n          ) : cell.result.data.length > 0 ? (\n            <CellResults\n              result={cell.result}\n              chartConfig={cell.chartConfig}\n              onChartConfigChange={handleChartConfigChange}\n            />\n          ) : (\n            <div className=\"px-3 py-2 text-xs text-muted-foreground\">\n              Query executed successfully. {cell.result.rowCount} rows returned.\n            </div>\n          )}\n        </div>\n      )}\n\n      {/* Add cell button between cells */}\n      <div className=\"absolute -bottom-3 left-1/2 -translate-x-1/2 z-10 opacity-0 group-hover:opacity-100 transition-opacity\">\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <Button\n              variant=\"outline\"\n              size=\"icon\"\n              className=\"h-5 w-5 rounded-full bg-background shadow-sm\"\n            >\n              <Plus className=\"h-3 w-3\" />\n            </Button>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align=\"center\" side=\"bottom\">\n            <DropdownMenuItem onClick={() => onAddCell(cell.id, \"sql\")}>\n              <Code className=\"h-4 w-4 mr-2\" />\n              SQL Cell\n            </DropdownMenuItem>\n            <DropdownMenuItem onClick={() => onAddCell(cell.id, \"markdown\")}>\n              <Type className=\"h-4 w-4 mr-2\" />\n              Markdown Cell\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n    </div>\n  );\n}\n\n// Results sub-component with table/chart tabs\nfunction CellResults({\n  result,\n  chartConfig,\n  onChartConfigChange,\n}: {\n  result: QueryResult;\n  chartConfig?: ChartConfig;\n  onChartConfigChange: (config: ChartConfig | undefined) => void;\n}) {\n  return (\n    <Tabs defaultValue=\"table\" className=\"w-full\">\n      <TabsList className=\"mx-2 mt-1 h-7\">\n        <TabsTrigger value=\"table\" className=\"text-xs h-6 gap-1 px-2\">\n          <Table className=\"w-3 h-3\" />\n          Table\n        </TabsTrigger>\n        <TabsTrigger value=\"chart\" className=\"text-xs h-6 gap-1 px-2\">\n          <BarChart3 className=\"w-3 h-3\" />\n          Chart\n        </TabsTrigger>\n      </TabsList>\n      <TabsContent value=\"table\" className=\"mt-0\">\n        <div className=\"max-h-[400px] overflow-auto\">\n          <ErrorBoundary FallbackComponent={CellErrorFallback}>\n            <DuckUiTable data={result.data} tableHeight={300} initialPageSize={50} />\n          </ErrorBoundary>\n        </div>\n      </TabsContent>\n      <TabsContent value=\"chart\" className=\"mt-0\">\n        <div className=\"h-[350px]\">\n          <ErrorBoundary FallbackComponent={CellErrorFallback}>\n            <ChartVisualizationPro\n              result={result}\n              chartConfig={chartConfig}\n              onConfigChange={onChartConfigChange}\n            />\n          </ErrorBoundary>\n        </div>\n      </TabsContent>\n    </Tabs>\n  );\n}\n"
  },
  {
    "path": "src/components/notebook/NotebookTab.tsx",
    "content": "import { useState, useCallback, useRef, useMemo } from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport type { NotebookCell } from \"@/store/types\";\nimport { NotebookCellComponent } from \"./NotebookCell\";\nimport { NotebookToolbar } from \"./NotebookToolbar\";\nimport { toast } from \"sonner\";\n\ninterface NotebookTabProps {\n  tabId: string;\n}\n\nexport default function NotebookTab({ tabId }: NotebookTabProps) {\n  const tabs = useDuckStore((s) => s.tabs);\n  const addCell = useDuckStore((s) => s.addNotebookCell);\n  const updateCellResult = useDuckStore((s) => s.updateNotebookCellResult);\n  const executeQuery = useDuckStore((s) => s.executeQuery);\n\n  const currentTab = tabs.find((t) => t.id === tabId);\n  const cells = useMemo((): NotebookCell[] => {\n    if (!currentTab || currentTab.type !== \"notebook\" || typeof currentTab.content !== \"string\") {\n      return [];\n    }\n    try {\n      return JSON.parse(currentTab.content) as NotebookCell[];\n    } catch {\n      return [];\n    }\n  }, [currentTab]);\n\n  const [runningCells, setRunningCells] = useState<Set<string>>(new Set());\n  const [isRunningAll, setIsRunningAll] = useState(false);\n  const abortRef = useRef(false);\n\n  const runCell = useCallback(\n    async (cellId: string) => {\n      const currentCells = useDuckStore.getState().getNotebookCells(tabId);\n      const cell = currentCells.find((c: NotebookCell) => c.id === cellId);\n      if (!cell || cell.type !== \"sql\" || !cell.content.trim()) return;\n\n      setRunningCells((prev) => new Set(prev).add(cellId));\n\n      try {\n        const result = await executeQuery(cell.content.trim());\n        if (result) {\n          updateCellResult(tabId, cellId, result);\n        }\n      } catch (error) {\n        updateCellResult(tabId, cellId, {\n          columns: [],\n          columnTypes: [],\n          data: [],\n          rowCount: 0,\n          error: error instanceof Error ? error.message : \"Unknown error\",\n        });\n      } finally {\n        setRunningCells((prev) => {\n          const next = new Set(prev);\n          next.delete(cellId);\n          return next;\n        });\n      }\n    },\n    [tabId, executeQuery, updateCellResult]\n  );\n\n  const runAllCells = useCallback(async () => {\n    const currentCells = useDuckStore.getState().getNotebookCells(tabId);\n    const sqlCells = currentCells.filter((c: NotebookCell) => c.type === \"sql\" && c.content.trim());\n    if (sqlCells.length === 0) return;\n\n    setIsRunningAll(true);\n    abortRef.current = false;\n\n    let completed = 0;\n    for (const cell of sqlCells) {\n      if (abortRef.current) break;\n      await runCell(cell.id);\n      completed++;\n    }\n\n    setIsRunningAll(false);\n    if (!abortRef.current) {\n      toast.success(`Executed ${completed} cell${completed !== 1 ? \"s\" : \"\"}`);\n    }\n  }, [tabId, runCell]);\n\n  const handleAddCell = useCallback(\n    (afterCellId: string, type: \"sql\" | \"markdown\") => {\n      addCell(tabId, afterCellId, type);\n    },\n    [addCell, tabId]\n  );\n\n  const handleAddCellAtEnd = useCallback(\n    (type: \"sql\" | \"markdown\") => {\n      const lastCell = cells[cells.length - 1];\n      addCell(tabId, lastCell?.id, type);\n    },\n    [addCell, tabId, cells]\n  );\n\n  return (\n    <div className=\"h-full flex flex-col\">\n      <NotebookToolbar\n        onRunAll={runAllCells}\n        onAddCell={handleAddCellAtEnd}\n        isRunning={isRunningAll}\n        cellCount={cells.length}\n      />\n\n      <div className=\"flex-1 overflow-y-auto\">\n        <div className=\"max-w-4xl mx-auto py-4 px-4 space-y-5\">\n          {cells.map((cell, index) => (\n            <NotebookCellComponent\n              key={cell.id}\n              cell={cell}\n              tabId={tabId}\n              cellIndex={index}\n              totalCells={cells.length}\n              isRunning={runningCells.has(cell.id)}\n              onRun={runCell}\n              onAddCell={handleAddCell}\n            />\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/notebook/NotebookToolbar.tsx",
    "content": "import { Play, Loader2, Plus, Code, Type } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\ninterface NotebookToolbarProps {\n  onRunAll: () => void;\n  onAddCell: (type: \"sql\" | \"markdown\") => void;\n  isRunning: boolean;\n  cellCount: number;\n}\n\nexport function NotebookToolbar({\n  onRunAll,\n  onAddCell,\n  isRunning,\n  cellCount,\n}: NotebookToolbarProps) {\n  return (\n    <div className=\"flex items-center gap-2 px-4 py-2 border-b bg-muted/30\">\n      <Button variant=\"outline\" size=\"sm\" onClick={onRunAll} disabled={isRunning} className=\"gap-2\">\n        {isRunning ? (\n          <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n        ) : (\n          <Play className=\"h-3.5 w-3.5\" />\n        )}\n        {isRunning ? \"Running...\" : \"Run All\"}\n      </Button>\n\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button variant=\"ghost\" size=\"sm\" className=\"gap-2\">\n            <Plus className=\"h-3.5 w-3.5\" />\n            Add Cell\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"start\">\n          <DropdownMenuItem onClick={() => onAddCell(\"sql\")}>\n            <Code className=\"h-4 w-4 mr-2\" />\n            SQL Cell\n          </DropdownMenuItem>\n          <DropdownMenuItem onClick={() => onAddCell(\"markdown\")}>\n            <Type className=\"h-4 w-4 mr-2\" />\n            Markdown Cell\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n\n      <span className=\"text-xs text-muted-foreground ml-auto\">\n        {cellCount} cell{cellCount !== 1 ? \"s\" : \"\"}\n      </span>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/profile/PasswordDialog.tsx",
    "content": "import { useState } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Loader2 } from \"lucide-react\";\nimport type { Profile } from \"@/store/types\";\nimport ProfileAvatar from \"./ProfileAvatar\";\n\ninterface PasswordDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  profile: Profile;\n  onSubmit: (password: string) => Promise<void>;\n}\n\nexport default function PasswordDialog({\n  open,\n  onOpenChange,\n  profile,\n  onSubmit,\n}: PasswordDialogProps) {\n  const [password, setPassword] = useState(\"\");\n  const [error, setError] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n\n  const handleSubmit = async () => {\n    if (!password.trim()) return;\n    setIsLoading(true);\n    setError(null);\n    try {\n      await onSubmit(password);\n      setPassword(\"\");\n    } catch {\n      setError(\"Incorrect password\");\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleOpenChange = (next: boolean) => {\n    if (!next) {\n      setPassword(\"\");\n      setError(null);\n    }\n    onOpenChange(next);\n  };\n\n  return (\n    <Dialog open={open} onOpenChange={handleOpenChange}>\n      <DialogContent className=\"sm:max-w-sm\">\n        <DialogHeader>\n          <DialogTitle>Unlock Profile</DialogTitle>\n          <DialogDescription className=\"sr-only\">\n            Enter your password to unlock this profile\n          </DialogDescription>\n        </DialogHeader>\n        <div className=\"space-y-4\">\n          <div className=\"flex flex-col items-center gap-2\">\n            <ProfileAvatar avatarEmoji={profile.avatarEmoji} size=\"xl\" />\n            <span className=\"font-medium\">{profile.name}</span>\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"profile-password\">Password</Label>\n            <Input\n              id=\"profile-password\"\n              type=\"password\"\n              value={password}\n              onChange={(e) => setPassword(e.target.value)}\n              onKeyDown={(e) => {\n                if (e.key === \"Enter\") handleSubmit();\n              }}\n              autoFocus\n              placeholder=\"Enter password\"\n            />\n          </div>\n          {error && (\n            <Alert variant=\"destructive\">\n              <AlertDescription>{error}</AlertDescription>\n            </Alert>\n          )}\n        </div>\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => handleOpenChange(false)} disabled={isLoading}>\n            Cancel\n          </Button>\n          <Button onClick={handleSubmit} disabled={!password.trim() || isLoading}>\n            {isLoading && <Loader2 className=\"h-4 w-4 mr-2 animate-spin\" />}\n            Unlock\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "src/components/profile/ProfileAvatar.tsx",
    "content": "import { useTheme } from \"@/components/theme/theme-provider\";\nimport { cn } from \"@/lib/utils\";\nimport Logo from \"/logo.png\";\nimport LogoLight from \"/logo-light.png\";\n\nconst sizeMap = {\n  sm: { img: \"h-4\", text: \"text-sm leading-none\" },\n  md: { img: \"h-6\", text: \"text-lg leading-none\" },\n  lg: { img: \"h-10\", text: \"text-3xl leading-none\" },\n  xl: { img: \"h-14\", text: \"text-5xl leading-none\" },\n};\n\ninterface ProfileAvatarProps {\n  avatarEmoji: string;\n  size?: keyof typeof sizeMap;\n  className?: string;\n}\n\nexport default function ProfileAvatar({ avatarEmoji, size = \"md\", className }: ProfileAvatarProps) {\n  const { theme } = useTheme();\n  const s = sizeMap[size];\n\n  if (avatarEmoji === \"logo\") {\n    return (\n      <img\n        src={theme === \"dark\" ? Logo : LogoLight}\n        alt=\"Duck-UI\"\n        className={cn(s.img, className)}\n      />\n    );\n  }\n\n  return <span className={cn(s.text, className)}>{avatarEmoji}</span>;\n}\n"
  },
  {
    "path": "src/components/profile/ProfileEditor.tsx",
    "content": "import { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport ProfileAvatar from \"./ProfileAvatar\";\n\nconst AVATAR_OPTIONS = [\n  \"logo\",\n  \"\\u{1F986}\",\n  \"\\u{1F424}\",\n  \"\\u{1F985}\",\n  \"\\u{1F427}\",\n  \"\\u{1F989}\",\n  \"\\u{1F426}\",\n  \"\\u{1F99C}\",\n  \"\\u{1F438}\",\n  \"\\u{1F43B}\",\n  \"\\u{1F98A}\",\n  \"\\u{1F431}\",\n  \"\\u{1F436}\",\n  \"\\u{1F43C}\",\n  \"\\u{1F981}\",\n  \"\\u{1F428}\",\n  \"\\u{1F42F}\",\n  \"\\u{1F430}\",\n  \"\\u{1F419}\",\n  \"\\u{1F98B}\",\n  \"\\u{1F31F}\",\n];\n\ninterface ProfileEditorProps {\n  mode: \"create\" | \"edit\";\n  initialValues?: { name: string; avatarEmoji: string };\n  onSave: (values: { name: string; avatarEmoji: string; password?: string }) => void;\n  onCancel: () => void;\n}\n\nexport default function ProfileEditor({\n  mode,\n  initialValues,\n  onSave,\n  onCancel,\n}: ProfileEditorProps) {\n  const [name, setName] = useState(initialValues?.name ?? \"\");\n  const [avatarEmoji, setAvatarEmoji] = useState(initialValues?.avatarEmoji ?? \"logo\");\n  const [passwordEnabled, setPasswordEnabled] = useState(false);\n  const [password, setPassword] = useState(\"\");\n  const [confirmPassword, setConfirmPassword] = useState(\"\");\n  const [emojiOpen, setEmojiOpen] = useState(false);\n\n  const passwordMismatch =\n    passwordEnabled && password !== confirmPassword && confirmPassword.length > 0;\n  const passwordTooShort = passwordEnabled && password.length > 0 && password.length < 4;\n\n  const isValid =\n    name.trim().length > 0 &&\n    (!passwordEnabled || (password.length >= 4 && password === confirmPassword));\n\n  const handleSave = () => {\n    if (!isValid) return;\n    onSave({\n      name: name.trim(),\n      avatarEmoji,\n      ...(passwordEnabled && password ? { password } : {}),\n    });\n  };\n\n  return (\n    <div className=\"space-y-6\">\n      <div className=\"space-y-2\">\n        <Label htmlFor=\"profile-name\">Name</Label>\n        <Input\n          id=\"profile-name\"\n          value={name}\n          onChange={(e) => setName(e.target.value)}\n          maxLength={30}\n          placeholder=\"Profile name\"\n          autoFocus={mode === \"create\"}\n        />\n      </div>\n\n      <div className=\"space-y-2\">\n        <Label>Avatar</Label>\n        <Popover open={emojiOpen} onOpenChange={setEmojiOpen}>\n          <PopoverTrigger asChild>\n            <Button variant=\"outline\" className=\"h-14 w-14 p-0 flex items-center justify-center\">\n              <ProfileAvatar avatarEmoji={avatarEmoji} size=\"lg\" />\n            </Button>\n          </PopoverTrigger>\n          <PopoverContent className=\"w-auto p-2\" align=\"start\">\n            <div className=\"grid grid-cols-5 gap-1\">\n              {AVATAR_OPTIONS.map((option) => (\n                <Button\n                  key={option}\n                  variant={option === avatarEmoji ? \"secondary\" : \"ghost\"}\n                  className=\"h-10 w-10 p-0 flex items-center justify-center\"\n                  onClick={() => {\n                    setAvatarEmoji(option);\n                    setEmojiOpen(false);\n                  }}\n                >\n                  <ProfileAvatar avatarEmoji={option} size=\"md\" />\n                </Button>\n              ))}\n            </div>\n          </PopoverContent>\n        </Popover>\n      </div>\n\n      {mode === \"create\" && (\n        <div className=\"space-y-4\">\n          <div className=\"flex items-center justify-between\">\n            <Label htmlFor=\"password-toggle\">Password Protection</Label>\n            <Switch\n              id=\"password-toggle\"\n              checked={passwordEnabled}\n              onCheckedChange={setPasswordEnabled}\n            />\n          </div>\n          {passwordEnabled && (\n            <div className=\"space-y-3\">\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"new-password\">Password</Label>\n                <Input\n                  id=\"new-password\"\n                  type=\"password\"\n                  value={password}\n                  onChange={(e) => setPassword(e.target.value)}\n                  placeholder=\"Min 4 characters\"\n                />\n                {passwordTooShort && (\n                  <p className=\"text-destructive text-sm\">Password must be at least 4 characters</p>\n                )}\n              </div>\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"confirm-password\">Confirm Password</Label>\n                <Input\n                  id=\"confirm-password\"\n                  type=\"password\"\n                  value={confirmPassword}\n                  onChange={(e) => setConfirmPassword(e.target.value)}\n                  placeholder=\"Confirm password\"\n                />\n                {passwordMismatch && (\n                  <p className=\"text-destructive text-sm\">Passwords don&apos;t match</p>\n                )}\n              </div>\n            </div>\n          )}\n        </div>\n      )}\n\n      <div className=\"flex justify-end gap-2\">\n        {mode === \"create\" && (\n          <Button variant=\"ghost\" onClick={onCancel}>\n            Cancel\n          </Button>\n        )}\n        <Button onClick={handleSave} disabled={!isValid}>\n          {mode === \"create\" ? \"Create Profile\" : \"Save\"}\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/profile/ProfilePicker.tsx",
    "content": "import { useState } from \"react\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { Lock, UserPlus, Loader2 } from \"lucide-react\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport type { Profile } from \"@/store/types\";\nimport PasswordDialog from \"./PasswordDialog\";\nimport ProfileEditor from \"./ProfileEditor\";\nimport ProfileAvatar from \"./ProfileAvatar\";\n\nimport Logo from \"/logo.png\";\n\ninterface ProfilePickerProps {\n  profiles: Profile[];\n  onSelectProfile: (profileId: string, password?: string) => Promise<void>;\n  onCreateProfile: (name: string, password?: string, avatarEmoji?: string) => Promise<string>;\n}\n\nexport default function ProfilePicker({\n  profiles,\n  onSelectProfile,\n  onCreateProfile,\n}: ProfilePickerProps) {\n  const [selectedProfile, setSelectedProfile] = useState<Profile | null>(null);\n  const [showPasswordDialog, setShowPasswordDialog] = useState(false);\n  const [showCreateDialog, setShowCreateDialog] = useState(profiles.length === 0);\n  const [isLoading, setIsLoading] = useState(false);\n\n  const handleSelectProfile = async (profile: Profile) => {\n    if (profile.hasPassword) {\n      setSelectedProfile(profile);\n      setShowPasswordDialog(true);\n    } else {\n      setIsLoading(true);\n      try {\n        await onSelectProfile(profile.id);\n      } finally {\n        setIsLoading(false);\n      }\n    }\n  };\n\n  const handlePasswordSubmit = async (password: string) => {\n    if (!selectedProfile) return;\n    await onSelectProfile(selectedProfile.id, password);\n    setShowPasswordDialog(false);\n  };\n\n  const handleCreate = async (values: { name: string; avatarEmoji: string; password?: string }) => {\n    setIsLoading(true);\n    try {\n      await onCreateProfile(values.name, values.password, values.avatarEmoji);\n      setShowCreateDialog(false);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"h-screen flex items-center justify-center bg-background text-foreground\">\n      <div className=\"text-center max-w-3xl w-full px-6 space-y-8\">\n        <img src={Logo} alt=\"Duck-UI\" className=\"h-16 mx-auto\" />\n        <h1 className=\"text-3xl font-bold tracking-tight\">\n          {profiles.length === 0 ? \"Welcome to Duck-UI\" : \"Choose Profile\"}\n        </h1>\n\n        {profiles.length > 0 && (\n          <>\n            <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n              {profiles.map((profile) => (\n                <Card\n                  key={profile.id}\n                  className=\"cursor-pointer hover:border-primary transition-colors\"\n                  onClick={() => !isLoading && handleSelectProfile(profile)}\n                >\n                  <CardContent className=\"p-6 flex flex-col items-center gap-2\">\n                    <ProfileAvatar avatarEmoji={profile.avatarEmoji} size=\"xl\" />\n                    <span className=\"font-medium text-lg\">{profile.name}</span>\n                    <span className=\"text-xs text-muted-foreground\">\n                      {formatDistanceToNow(new Date(profile.lastActive), { addSuffix: true })}\n                    </span>\n                    {profile.hasPassword && (\n                      <Badge variant=\"secondary\" className=\"gap-1\">\n                        <Lock className=\"h-3 w-3\" />\n                        Protected\n                      </Badge>\n                    )}\n                  </CardContent>\n                </Card>\n              ))}\n            </div>\n\n            {isLoading && (\n              <div className=\"flex items-center justify-center gap-2 text-muted-foreground\">\n                <Loader2 className=\"h-4 w-4 animate-spin\" />\n                <span>Loading profile...</span>\n              </div>\n            )}\n\n            <Separator />\n          </>\n        )}\n\n        <Button variant=\"outline\" onClick={() => setShowCreateDialog(true)} disabled={isLoading}>\n          <UserPlus className=\"h-4 w-4 mr-2\" />\n          Create New Profile\n        </Button>\n        <p className=\"text-xs text-muted-foreground\">\n          Your profiles are stored locally on your device. Creating a profile does not share any\n          data... This application is 100% offline and private by design, it's runs on the client\n          side and does not send any data to any server.\n        </p>\n      </div>\n\n      {selectedProfile && (\n        <PasswordDialog\n          open={showPasswordDialog}\n          onOpenChange={setShowPasswordDialog}\n          profile={selectedProfile}\n          onSubmit={handlePasswordSubmit}\n        />\n      )}\n\n      <Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>\n        <DialogContent className=\"sm:max-w-md\">\n          <DialogHeader>\n            <DialogTitle>Create Profile</DialogTitle>\n            <DialogDescription className=\"sr-only\">Create a new user profile</DialogDescription>\n          </DialogHeader>\n          <ProfileEditor\n            mode=\"create\"\n            onSave={handleCreate}\n            onCancel={() => setShowCreateDialog(false)}\n          />\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/saved-queries/SaveQueryDialog.tsx",
    "content": "import { useState, useEffect } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Loader2 } from \"lucide-react\";\nimport { toast } from \"sonner\";\nimport { useDuckStore } from \"@/store\";\nimport { saveQuery } from \"@/services/persistence/repositories/savedQueryRepository\";\n\ninterface SaveQueryDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  defaultName: string;\n  sqlText: string;\n}\n\nexport default function SaveQueryDialog({\n  open,\n  onOpenChange,\n  defaultName,\n  sqlText,\n}: SaveQueryDialogProps) {\n  const [name, setName] = useState(defaultName);\n  const [description, setDescription] = useState(\"\");\n  const [isSaving, setIsSaving] = useState(false);\n  const currentProfileId = useDuckStore((s) => s.currentProfileId);\n  const bumpSavedQueriesVersion = useDuckStore((s) => s.bumpSavedQueriesVersion);\n\n  // Reset form when dialog opens\n  useEffect(() => {\n    if (open) {\n      setName(defaultName);\n      setDescription(\"\");\n    }\n  }, [open, defaultName]);\n\n  const handleSave = async () => {\n    if (!name.trim() || !currentProfileId) return;\n    setIsSaving(true);\n    try {\n      await saveQuery(currentProfileId, {\n        name: name.trim(),\n        sqlText,\n        description: description.trim() || undefined,\n      });\n      bumpSavedQueriesVersion();\n      toast.success(\"Query saved\");\n      onOpenChange(false);\n    } catch {\n      toast.error(\"Failed to save query\");\n    } finally {\n      setIsSaving(false);\n    }\n  };\n\n  return (\n    <Dialog open={open} onOpenChange={onOpenChange}>\n      <DialogContent className=\"sm:max-w-md\">\n        <DialogHeader>\n          <DialogTitle>Save Query</DialogTitle>\n          <DialogDescription className=\"sr-only\">\n            Save the current query for later use\n          </DialogDescription>\n        </DialogHeader>\n        <div className=\"space-y-4\">\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"query-name\">Name</Label>\n            <Input\n              id=\"query-name\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n              placeholder=\"Query name\"\n              autoFocus\n              onKeyDown={(e) => {\n                if (e.key === \"Enter\") handleSave();\n              }}\n            />\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"query-description\">Description (optional)</Label>\n            <Textarea\n              id=\"query-description\"\n              value={description}\n              onChange={(e) => setDescription(e.target.value)}\n              placeholder=\"What does this query do?\"\n              rows={2}\n            />\n          </div>\n        </div>\n        <DialogFooter>\n          <Button variant=\"ghost\" onClick={() => onOpenChange(false)} disabled={isSaving}>\n            Cancel\n          </Button>\n          <Button onClick={handleSave} disabled={!name.trim() || isSaving}>\n            {isSaving && <Loader2 className=\"h-4 w-4 mr-2 animate-spin\" />}\n            Save\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "src/components/saved-queries/SavedQueriesPanel.tsx",
    "content": "import { useState, useEffect, useRef } from \"react\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { MoreVertical, Bookmark, X } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { toast } from \"sonner\";\nimport { useDuckStore } from \"@/store\";\nimport {\n  getSavedQueries,\n  updateSavedQuery,\n  deleteSavedQuery,\n  type SavedQuery,\n} from \"@/services/persistence/repositories/savedQueryRepository\";\n\ninterface SavedQueriesPanelProps {\n  onClose: () => void;\n}\n\nexport default function SavedQueriesPanel({ onClose }: SavedQueriesPanelProps) {\n  const currentProfileId = useDuckStore((s) => s.currentProfileId);\n  const createTab = useDuckStore((s) => s.createTab);\n  const savedQueriesVersion = useDuckStore((s) => s.savedQueriesVersion);\n  const bumpSavedQueriesVersion = useDuckStore((s) => s.bumpSavedQueriesVersion);\n  const [queries, setQueries] = useState<SavedQuery[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [editingId, setEditingId] = useState<string | null>(null);\n  const [editName, setEditName] = useState(\"\");\n  const renameInputRef = useRef<HTMLInputElement>(null);\n\n  useEffect(() => {\n    if (!currentProfileId) return;\n    getSavedQueries(currentProfileId)\n      .then(setQueries)\n      .catch(console.error)\n      .finally(() => setLoading(false));\n  }, [currentProfileId, savedQueriesVersion]);\n\n  useEffect(() => {\n    if (editingId && renameInputRef.current) {\n      renameInputRef.current.focus();\n      renameInputRef.current.select();\n    }\n  }, [editingId]);\n\n  const handleOpen = (query: SavedQuery) => {\n    createTab(\"sql\", query.sql_text, query.name);\n  };\n\n  const handleStartRename = (query: SavedQuery) => {\n    setEditingId(query.id);\n    setEditName(query.name);\n  };\n\n  const handleFinishRename = async (id: string) => {\n    if (!editName.trim()) {\n      setEditingId(null);\n      return;\n    }\n    try {\n      await updateSavedQuery(id, { name: editName.trim() });\n      setQueries((prev) => prev.map((q) => (q.id === id ? { ...q, name: editName.trim() } : q)));\n      bumpSavedQueriesVersion();\n    } catch {\n      toast.error(\"Failed to rename query\");\n    }\n    setEditingId(null);\n  };\n\n  const handleDelete = async (id: string) => {\n    try {\n      await deleteSavedQuery(id);\n      setQueries((prev) => prev.filter((q) => q.id !== id));\n      bumpSavedQueriesVersion();\n      toast.success(\"Query deleted\");\n    } catch {\n      toast.error(\"Failed to delete query\");\n    }\n  };\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <div className=\"flex items-center justify-between px-4 py-2 border-b\">\n        <span className=\"text-sm font-medium flex items-center gap-2\">\n          <Bookmark className=\"h-4 w-4\" />\n          Saved Queries\n        </span>\n        <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6\" onClick={onClose}>\n          <X className=\"h-4 w-4\" />\n          <span className=\"sr-only\">Close</span>\n        </Button>\n      </div>\n\n      <ScrollArea className=\"flex-1\">\n        {loading ? (\n          <div className=\"p-3 space-y-3\">\n            {[1, 2, 3].map((i) => (\n              <Skeleton key={i} className=\"h-20 w-full rounded-md\" />\n            ))}\n          </div>\n        ) : queries.length === 0 ? (\n          <div className=\"p-8 text-center text-muted-foreground text-sm\">\n            <Bookmark className=\"h-8 w-8 mx-auto mb-3 opacity-50\" />\n            <p>No saved queries yet</p>\n            <p className=\"text-xs mt-1\">Save a query from the editor toolbar</p>\n          </div>\n        ) : (\n          <div className=\"space-y-2 p-3\">\n            {queries.map((query) => (\n              <Card\n                key={query.id}\n                className=\"cursor-pointer hover:bg-accent/50 transition-colors\"\n                onClick={() => handleOpen(query)}\n              >\n                <CardContent className=\"p-3\">\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <div className=\"flex-1 min-w-0 space-y-1\">\n                      {editingId === query.id ? (\n                        <Input\n                          ref={renameInputRef}\n                          value={editName}\n                          onChange={(e) => setEditName(e.target.value)}\n                          onBlur={() => handleFinishRename(query.id)}\n                          onKeyDown={(e) => {\n                            if (e.key === \"Enter\") handleFinishRename(query.id);\n                            if (e.key === \"Escape\") setEditingId(null);\n                          }}\n                          onClick={(e) => e.stopPropagation()}\n                          className=\"h-6 text-sm font-medium px-1\"\n                        />\n                      ) : (\n                        <span className=\"font-medium text-sm truncate block\">{query.name}</span>\n                      )}\n                      <pre className=\"text-xs font-mono text-muted-foreground truncate\">\n                        {query.sql_text.length > 100\n                          ? query.sql_text.slice(0, 100) + \"...\"\n                          : query.sql_text}\n                      </pre>\n                      <span className=\"text-xs text-muted-foreground\">\n                        {formatDistanceToNow(new Date(query.updated_at), { addSuffix: true })}\n                      </span>\n                    </div>\n                    <DropdownMenu>\n                      <DropdownMenuTrigger asChild>\n                        <Button\n                          variant=\"ghost\"\n                          size=\"icon\"\n                          className=\"h-7 w-7 shrink-0\"\n                          onClick={(e) => e.stopPropagation()}\n                        >\n                          <MoreVertical className=\"h-4 w-4\" />\n                        </Button>\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent align=\"end\">\n                        <DropdownMenuItem\n                          onClick={(e) => {\n                            e.stopPropagation();\n                            handleOpen(query);\n                          }}\n                        >\n                          Open in new tab\n                        </DropdownMenuItem>\n                        <DropdownMenuItem\n                          onClick={(e) => {\n                            e.stopPropagation();\n                            handleStartRename(query);\n                          }}\n                        >\n                          Rename\n                        </DropdownMenuItem>\n                        <DropdownMenuSeparator />\n                        <DropdownMenuItem\n                          className=\"text-destructive\"\n                          onClick={(e) => {\n                            e.stopPropagation();\n                            handleDelete(query.id);\n                          }}\n                        >\n                          Delete\n                        </DropdownMenuItem>\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </div>\n                </CardContent>\n              </Card>\n            ))}\n          </div>\n        )}\n      </ScrollArea>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/table/CellValueViewer.tsx",
    "content": "import React, { useState, useMemo } from \"react\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Button } from \"@/components/ui/button\";\nimport { Copy, X, ChevronDown, ChevronUp } from \"lucide-react\";\nimport { toast } from \"sonner\";\n\ninterface CellValueViewerProps {\n  value: unknown;\n  columnName: string;\n  rowIndex: number;\n  onClose: () => void;\n}\n\nexport const CellValueViewer: React.FC<CellValueViewerProps> = ({\n  value,\n  columnName,\n  rowIndex,\n  onClose,\n}) => {\n  const [isMinimized, setIsMinimized] = useState(false);\n\n  // Detect value type and format accordingly\n  // eslint-disable-next-line react-hooks/preserve-manual-memoization\n  const { displayValue, valueType } = useMemo(() => {\n    if (value === null || value === undefined) {\n      return { displayValue: \"NULL\", valueType: \"null\", isFormatted: false };\n    }\n\n    // Check if it's JSON\n    if (typeof value === \"object\") {\n      try {\n        return {\n          displayValue: JSON.stringify(value, null, 2),\n          valueType: \"json\",\n          isFormatted: true,\n        };\n      } catch {\n        return {\n          displayValue: String(value),\n          valueType: \"object\",\n          isFormatted: false,\n        };\n      }\n    }\n\n    // Check if string value is JSON\n    if (typeof value === \"string\") {\n      try {\n        const parsed = JSON.parse(value);\n        if (typeof parsed === \"object\") {\n          return {\n            displayValue: JSON.stringify(parsed, null, 2),\n            valueType: \"json\",\n            isFormatted: true,\n          };\n        }\n      } catch {\n        // Not JSON, return as string\n      }\n\n      // Check if it's a long text\n      if (value.length > 100) {\n        return {\n          displayValue: value,\n          valueType: \"text\",\n          isFormatted: false,\n        };\n      }\n    }\n\n    // Check if it's a number\n    if (typeof value === \"number\" || typeof value === \"bigint\") {\n      return {\n        displayValue: String(value),\n        valueType: \"number\",\n        isFormatted: false,\n      };\n    }\n\n    // Default string\n    return {\n      displayValue: String(value),\n      valueType: \"string\",\n      isFormatted: false,\n    };\n  }, [value]);\n\n  const handleCopy = () => {\n    navigator.clipboard.writeText(displayValue);\n    toast.success(\"Copied to clipboard\");\n  };\n\n  if (isMinimized) {\n    return (\n      <Card className=\"fixed bottom-4 right-4 z-30 w-[300px] shadow-lg border\">\n        <CardHeader className=\"p-3 cursor-pointer\" onClick={() => setIsMinimized(false)}>\n          <div className=\"flex items-center justify-between\">\n            <CardTitle className=\"text-sm font-medium truncate\">\n              {columnName} (Row {rowIndex + 1})\n            </CardTitle>\n            <div className=\"flex gap-1\">\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"h-6 w-6 p-0\"\n                onClick={(e) => {\n                  e.stopPropagation();\n                  setIsMinimized(false);\n                }}\n              >\n                <ChevronUp className=\"h-4 w-4\" />\n              </Button>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"h-6 w-6 p-0\"\n                onClick={(e) => {\n                  e.stopPropagation();\n                  onClose();\n                }}\n              >\n                <X className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          </div>\n        </CardHeader>\n      </Card>\n    );\n  }\n\n  return (\n    <Card className=\"fixed bottom-4 right-4 z-30 w-[500px] max-h-[400px] shadow-lg border\">\n      <CardHeader className=\"p-3 border-b\">\n        <div className=\"flex items-center justify-between\">\n          <div className=\"flex flex-col gap-0.5\">\n            <CardTitle className=\"text-sm font-medium\">Cell Value</CardTitle>\n            <p className=\"text-xs text-muted-foreground\">\n              {columnName} • Row {rowIndex + 1} • {valueType}\n            </p>\n          </div>\n          <div className=\"flex gap-1\">\n            <Button variant=\"ghost\" size=\"sm\" className=\"h-7 px-2 text-xs\" onClick={handleCopy}>\n              <Copy className=\"h-3 w-3 mr-1\" />\n              Copy\n            </Button>\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              className=\"h-7 w-7 p-0\"\n              onClick={() => setIsMinimized(true)}\n            >\n              <ChevronDown className=\"h-4 w-4\" />\n            </Button>\n            <Button variant=\"ghost\" size=\"sm\" className=\"h-7 w-7 p-0\" onClick={onClose}>\n              <X className=\"h-4 w-4\" />\n            </Button>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent className=\"p-3\">\n        <div className=\"max-h-[300px] overflow-auto\">\n          {valueType === \"json\" ? (\n            <pre className=\"text-xs font-mono bg-muted/50 p-3 rounded-md overflow-x-auto\">\n              <code className=\"language-json\">{displayValue}</code>\n            </pre>\n          ) : valueType === \"text\" ? (\n            <div className=\"text-xs whitespace-pre-wrap bg-muted/50 p-3 rounded-md\">\n              {displayValue}\n            </div>\n          ) : valueType === \"null\" ? (\n            <div className=\"text-xs text-muted-foreground italic p-3\">{displayValue}</div>\n          ) : (\n            <div className=\"text-xs font-mono bg-muted/50 p-3 rounded-md break-all\">\n              {displayValue}\n            </div>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n};\n\nexport default CellValueViewer;\n"
  },
  {
    "path": "src/components/table/ColumnStatsPanel.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Button } from \"@/components/ui/button\";\nimport { ChevronDown, ChevronUp, X } from \"lucide-react\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\n\ntype DataRow = Record<string, unknown>;\n\ninterface ColumnStats {\n  columnName: string;\n  totalCount: number;\n  nullCount: number;\n  uniqueCount: number;\n  dataType: string;\n  min?: number | string;\n  max?: number | string;\n  avg?: number;\n  topValues?: { value: unknown; count: number }[];\n}\n\ninterface ColumnStatsPanelProps {\n  data: DataRow[];\n  onClose: () => void;\n  isMinimized: boolean;\n  onToggleMinimize: () => void;\n}\n\nconst calculateColumnStats = (data: DataRow[], columnName: string): ColumnStats => {\n  const values = data.map((row) => row[columnName]);\n  const totalCount = values.length;\n  const nullCount = values.filter((v) => v === null || v === undefined).length;\n  const nonNullValues = values.filter((v) => v !== null && v !== undefined);\n  const uniqueValues = new Set(nonNullValues);\n  const uniqueCount = uniqueValues.size;\n\n  // Determine data type\n  let dataType = \"mixed\";\n  if (nonNullValues.length > 0) {\n    const firstValue = nonNullValues[0];\n    if (typeof firstValue === \"number\" || typeof firstValue === \"bigint\") {\n      dataType = \"number\";\n    } else if (typeof firstValue === \"string\") {\n      dataType = \"string\";\n    } else if (typeof firstValue === \"boolean\") {\n      dataType = \"boolean\";\n    } else if (firstValue instanceof Date) {\n      dataType = \"date\";\n    }\n  }\n\n  const stats: ColumnStats = {\n    columnName,\n    totalCount,\n    nullCount,\n    uniqueCount,\n    dataType,\n  };\n\n  // Calculate stats for numeric columns\n  if (dataType === \"number\") {\n    const numericValues = nonNullValues.map((v) =>\n      typeof v === \"bigint\" ? Number(v) : v\n    ) as number[];\n    if (numericValues.length > 0) {\n      stats.min = Math.min(...numericValues);\n      stats.max = Math.max(...numericValues);\n      stats.avg = numericValues.reduce((sum, v) => sum + v, 0) / numericValues.length;\n    }\n  }\n\n  // Calculate top values (for categorical data)\n  if (dataType === \"string\" || dataType === \"boolean\" || uniqueCount <= 20) {\n    const valueCounts = new Map<string, number>();\n    nonNullValues.forEach((value) => {\n      const key = String(value);\n      valueCounts.set(key, (valueCounts.get(key) || 0) + 1);\n    });\n\n    stats.topValues = Array.from(valueCounts.entries())\n      .map(([value, count]) => ({ value, count }))\n      .sort((a, b) => b.count - a.count)\n      .slice(0, 10);\n  }\n\n  return stats;\n};\n\nconst MiniChart: React.FC<{ stats: ColumnStats }> = ({ stats }) => {\n  const { theme } = useTheme();\n\n  if (!stats.topValues || stats.topValues.length === 0) {\n    return <div className=\"text-xs text-muted-foreground italic\">No chart data available</div>;\n  }\n\n  const chartData = stats.topValues.slice(0, 8).map((item) => ({\n    name:\n      String(item.value).length > 15\n        ? String(item.value).substring(0, 15) + \"...\"\n        : String(item.value),\n    value: item.count,\n  }));\n\n  const maxValue = Math.max(...chartData.map((d) => d.value));\n\n  const colors =\n    theme === \"dark\"\n      ? [\"#8b5cf6\", \"#a78bfa\", \"#c4b5fd\", \"#ddd6fe\", \"#ede9fe\"]\n      : [\"#7c3aed\", \"#8b5cf6\", \"#a78bfa\", \"#c4b5fd\", \"#ddd6fe\"];\n\n  return (\n    <div className=\"space-y-1\">\n      <div className=\"flex items-end gap-0.5 h-[80px]\">\n        {chartData.map((item, i) => (\n          <div\n            key={i}\n            className=\"flex-1 rounded-t transition-opacity hover:opacity-80\"\n            style={{\n              height: `${maxValue > 0 ? (item.value / maxValue) * 100 : 0}%`,\n              backgroundColor: colors[i % colors.length],\n              minHeight: item.value > 0 ? 2 : 0,\n            }}\n            title={`${item.name}: ${item.value}`}\n          />\n        ))}\n      </div>\n      <div className=\"flex gap-0.5\">\n        {chartData.map((item, i) => (\n          <div\n            key={i}\n            className=\"flex-1 text-[8px] text-muted-foreground truncate text-center\"\n            title={item.name}\n          >\n            {item.name}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nconst ColumnStatCard: React.FC<{ stats: ColumnStats }> = ({ stats }) => {\n  return (\n    <Card className=\"w-full\">\n      <CardHeader className=\"p-3 pb-2\">\n        <CardTitle className=\"text-sm font-medium truncate\" title={stats.columnName}>\n          {stats.columnName}\n        </CardTitle>\n        <p className=\"text-xs text-muted-foreground\">{stats.dataType}</p>\n      </CardHeader>\n      <CardContent className=\"p-3 pt-0 space-y-2\">\n        <div className=\"grid grid-cols-2 gap-2 text-xs\">\n          <div>\n            <span className=\"text-muted-foreground\">Total:</span>{\" \"}\n            <span className=\"font-medium\">{stats.totalCount.toLocaleString()}</span>\n          </div>\n          <div>\n            <span className=\"text-muted-foreground\">Unique:</span>{\" \"}\n            <span className=\"font-medium\">{stats.uniqueCount.toLocaleString()}</span>\n          </div>\n          <div>\n            <span className=\"text-muted-foreground\">Nulls:</span>{\" \"}\n            <span className=\"font-medium\">{stats.nullCount.toLocaleString()}</span>\n          </div>\n          <div>\n            <span className=\"text-muted-foreground\">Fill:</span>{\" \"}\n            <span className=\"font-medium\">\n              {(((stats.totalCount - stats.nullCount) / stats.totalCount) * 100 || 0).toFixed(1)}%\n            </span>\n          </div>\n        </div>\n\n        {stats.dataType === \"number\" && stats.min !== undefined && (\n          <div className=\"text-xs space-y-1 pt-1 border-t\">\n            <div>\n              <span className=\"text-muted-foreground\">Min:</span>{\" \"}\n              <span className=\"font-mono\">{Number(stats.min).toLocaleString()}</span>\n            </div>\n            <div>\n              <span className=\"text-muted-foreground\">Max:</span>{\" \"}\n              <span className=\"font-mono\">{Number(stats.max).toLocaleString()}</span>\n            </div>\n            {stats.avg !== undefined && (\n              <div>\n                <span className=\"text-muted-foreground\">Avg:</span>{\" \"}\n                <span className=\"font-mono\">\n                  {stats.avg.toLocaleString(undefined, { maximumFractionDigits: 2 })}\n                </span>\n              </div>\n            )}\n          </div>\n        )}\n\n        <div className=\"pt-2\">\n          <MiniChart stats={stats} />\n        </div>\n      </CardContent>\n    </Card>\n  );\n};\n\nexport const ColumnStatsPanel: React.FC<ColumnStatsPanelProps> = ({\n  data,\n  onClose,\n  isMinimized,\n  onToggleMinimize,\n}) => {\n  const columnStats = useMemo(() => {\n    if (!data || data.length === 0 || !data[0]) return [];\n\n    const columns = Object.keys(data[0]).filter((key) => key !== \"__row_number__\");\n    return columns.map((col) => calculateColumnStats(data, col));\n  }, [data]);\n\n  if (isMinimized) {\n    return (\n      <Card className=\"fixed bottom-4 left-4 z-30 w-[300px] shadow-lg border\">\n        <CardHeader className=\"p-3 cursor-pointer\" onClick={onToggleMinimize}>\n          <div className=\"flex items-center justify-between\">\n            <CardTitle className=\"text-sm font-medium\">\n              Column Statistics ({columnStats.length} columns)\n            </CardTitle>\n            <div className=\"flex gap-1\">\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"h-6 w-6 p-0\"\n                onClick={(e) => {\n                  e.stopPropagation();\n                  onToggleMinimize();\n                }}\n              >\n                <ChevronUp className=\"h-4 w-4\" />\n              </Button>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"h-6 w-6 p-0\"\n                onClick={(e) => {\n                  e.stopPropagation();\n                  onClose();\n                }}\n              >\n                <X className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          </div>\n        </CardHeader>\n      </Card>\n    );\n  }\n\n  return (\n    <Card className=\"fixed bottom-4 left-4 z-30 w-7xl shadow-lg border\">\n      <CardHeader className=\"p-3 border-b\">\n        <div className=\"flex items-center justify-between\">\n          <div>\n            <CardTitle className=\"text-sm font-medium\">Column Statistics</CardTitle>\n            <p className=\"text-xs text-muted-foreground mt-0.5\">\n              {columnStats.length} columns • {data.length.toLocaleString()} rows\n            </p>\n          </div>\n          <div className=\"flex gap-1\">\n            <Button variant=\"ghost\" size=\"sm\" className=\"h-7 w-7 p-0\" onClick={onToggleMinimize}>\n              <ChevronDown className=\"h-4 w-4\" />\n            </Button>\n            <Button variant=\"ghost\" size=\"sm\" className=\"h-7 w-7 p-0\" onClick={onClose}>\n              <X className=\"h-4 w-4\" />\n            </Button>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent className=\"p-3\">\n        <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 max-h-[500px] overflow-auto\">\n          {columnStats.map((stats) => (\n            <ColumnStatCard key={stats.columnName} stats={stats} />\n          ))}\n        </div>\n      </CardContent>\n    </Card>\n  );\n};\n\nexport default ColumnStatsPanel;\n"
  },
  {
    "path": "src/components/table/DuckUItable.tsx",
    "content": "import React, { useState, useMemo, useEffect, useRef } from \"react\";\nimport {\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useReactTable,\n  type ColumnDef,\n  type FilterFn,\n  type ColumnFiltersState,\n  type ColumnResizeMode,\n  type PaginationState,\n  type ColumnSizingState,\n  type SortingState,\n} from \"@tanstack/react-table\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { formatBytes, formatDuration } from \"@/lib/utils\";\nimport {\n  Download,\n  Search,\n  X,\n  Clock,\n  SlidersHorizontal,\n  Grid3X3,\n  Copy,\n  MousePointer,\n  MoreHorizontal,\n  ChevronDown,\n  ChevronUp,\n  ChevronsUpDown,\n  BarChart3,\n  FolderOpen,\n  FileSpreadsheet,\n  FileJson,\n  FileText,\n  FileArchive,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n  DropdownMenuSeparator,\n} from \"@/components/ui/dropdown-menu\";\nimport { toast } from \"sonner\";\nimport { useDuckStore } from \"@/store\";\nimport { CellValueViewer } from \"./CellValueViewer\";\nimport { ColumnStatsPanel } from \"./ColumnStatsPanel\";\nimport { fileSystemService } from \"@/lib/fileSystem\";\n\n// Define a generic type for the data row\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype DataRow = Record<string, any>;\n\n// Types for cell selection\ntype CellPosition = { row: number; col: string };\ntype ContextMenuPosition = { x: number; y: number };\n\n// Type for the component props\ninterface DuckTableProps {\n  data: DataRow[];\n  executionTime?: number | null;\n  responseSize?: number | null;\n  initialPageSize?: number;\n  columnRenderers?: Record<string, (value: unknown) => React.ReactNode>;\n  tableHeight?: string | number;\n}\n\nconst globalFilterFn: FilterFn<DataRow> = (row, columnId, value) => {\n  const cellValue = row.getValue(columnId);\n  if (cellValue === null || cellValue === undefined) return false;\n  const searchStr = String(cellValue).toLowerCase();\n  const filterValue = String(value).toLowerCase();\n  return searchStr.includes(filterValue);\n};\n\nconst DEFAULT_COLUMN_WIDTH = 150;\nconst MIN_COLUMN_WIDTH = 50;\nconst MAX_COLUMN_WIDTH = 1000; // Generous max for \"free\" resizing\nconst DEFAULT_MAX_AUTO_WIDTH = 250;\nconst DEFAULT_MIN_AUTO_WIDTH = 80;\nconst DEFAULT_SAMPLE_SIZE = 100;\n\n// Safe JSON stringify that handles BigInt\nconst safeStringify = (value: unknown): string => {\n  if (value === null || value === undefined) return \"null\";\n  if (typeof value === \"bigint\") return value.toString();\n  if (typeof value === \"object\") {\n    try {\n      return JSON.stringify(value, (_, v) => (typeof v === \"bigint\" ? v.toString() : v));\n    } catch {\n      return String(value);\n    }\n  }\n  return String(value);\n};\n\n// Calculate optimal column width based on content\nconst calculateOptimalWidth = (\n  data: DataRow[],\n  columnKey: string,\n  minWidth: number = DEFAULT_MIN_AUTO_WIDTH,\n  maxWidth: number = DEFAULT_MAX_AUTO_WIDTH,\n  sampleSize: number = DEFAULT_SAMPLE_SIZE\n): number => {\n  if (!data.length) return DEFAULT_COLUMN_WIDTH;\n\n  // Sample data for performance\n  const sample = data.slice(0, Math.min(sampleSize, data.length));\n\n  // Calculate based on content length\n  let maxLength = columnKey.length; // Start with header length\n\n  sample.forEach((row) => {\n    const value = row[columnKey];\n    const strValue = safeStringify(value);\n    maxLength = Math.max(maxLength, strValue.length);\n  });\n\n  // Rough estimation: 8px per character + padding\n  const estimatedWidth = Math.max(minWidth, Math.min(maxWidth, maxLength * 8 + 20));\n\n  return estimatedWidth;\n};\n\nconst DuckUITable: React.FC<DuckTableProps> = ({\n  data = [],\n  executionTime,\n  responseSize,\n  initialPageSize = 25,\n  columnRenderers,\n  tableHeight = \"100%\",\n}) => {\n  const tableContainerRef = useRef<HTMLDivElement>(null);\n  const horizontalScrollRef = useRef<HTMLDivElement>(null);\n\n  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);\n  const [globalFilter, setGlobalFilter] = useState(\"\");\n  const [globalFilterInput, setGlobalFilterInput] = useState(\"\");\n\n  const [pagination, setPagination] = useState<PaginationState>({\n    pageIndex: 0,\n    pageSize: initialPageSize,\n  });\n  const [enabledColumns, setEnabledColumns] = useState<Record<string, boolean>>({});\n  const [showColumnSelector, setShowColumnSelector] = useState(false);\n  const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});\n  const [userResizedColumns, setUserResizedColumns] = useState<Record<string, number>>({});\n  const [columnSelectorFilter, setColumnSelectorFilter] = useState(\"\");\n  const [sorting, setSorting] = useState<SortingState>([]);\n\n  // New spreadsheet features\n  const [showRowNumbers, setShowRowNumbers] = useState(false);\n  const [zebraStripes, setZebraStripes] = useState(true);\n  const [showGridLines, setShowGridLines] = useState(false);\n  const [showSpreadsheetOptions, setShowSpreadsheetOptions] = useState(false);\n\n  // Cell selection state\n  const [selectedCells, setSelectedCells] = useState<Set<string>>(new Set());\n  const [lastSelectedCell, setLastSelectedCell] = useState<CellPosition | null>(null);\n  const [contextMenu, setContextMenu] = useState<ContextMenuPosition | null>(null);\n  // Drag selection state\n  const [isDragging, setIsDragging] = useState(false);\n  const [dragStart, setDragStart] = useState<CellPosition | null>(null);\n  const [dragEnd, setDragEnd] = useState<CellPosition | null>(null);\n\n  // Cell value viewer state\n  const [viewedCell, setViewedCell] = useState<{\n    value: unknown;\n    columnName: string;\n    rowIndex: number;\n  } | null>(null);\n\n  // Get mounted folders from store\n  const mountedFolders = useDuckStore((state) => state.mountedFolders);\n\n  // Column stats panel state\n  const [showStatsPanel, setShowStatsPanel] = useState(false);\n  const [statsPanelMinimized, setStatsPanelMinimized] = useState(false);\n\n  const columnResizeMode = \"onChange\" as ColumnResizeMode;\n\n  useEffect(() => {\n    const handler = setTimeout(() => setGlobalFilter(globalFilterInput), 300);\n    return () => clearTimeout(handler);\n  }, [globalFilterInput]);\n\n  useEffect(() => {\n    if (globalFilter !== globalFilterInput) setGlobalFilterInput(globalFilter);\n  }, [globalFilter]);\n\n  // Initialize/Update enabled columns when data changes\n  useEffect(() => {\n    if (data && data.length > 0 && data[0]) {\n      const allKeys = Object.keys(data[0]);\n      const initialEnabledCols = allKeys.reduce(\n        (acc, key) => {\n          acc[key] = enabledColumns[key] === undefined ? true : enabledColumns[key];\n          return acc;\n        },\n        {} as Record<string, boolean>\n      );\n      setEnabledColumns(initialEnabledCols);\n\n      const validUserResized: Record<string, number> = {};\n      allKeys.forEach((key) => {\n        if (userResizedColumns[key] !== undefined) {\n          validUserResized[key] = userResizedColumns[key];\n        }\n      });\n      setUserResizedColumns(validUserResized);\n    } else {\n      setEnabledColumns({});\n      setUserResizedColumns({});\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [data]); // Keep enabledColumns and userResizedColumns out to preserve user settings\n\n  // Effect to initialize columnSizing or update it when userResizedColumns change\n  useEffect(() => {\n    if (data && data.length > 0 && data[0]) {\n      const newSizingState: ColumnSizingState = {};\n      Object.keys(data[0]).forEach((key) => {\n        newSizingState[key] =\n          userResizedColumns[key] !== undefined ? userResizedColumns[key] : DEFAULT_COLUMN_WIDTH; // Use default width\n      });\n      if (JSON.stringify(columnSizing) !== JSON.stringify(newSizingState)) {\n        setColumnSizing(newSizingState);\n      }\n    } else if (Object.keys(columnSizing).length > 0) {\n      setColumnSizing({});\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [data, userResizedColumns]); // columnSizing itself should not be a direct dependency here to avoid loops\n\n  // Close column selector and context menu on outside click (Issue #5, #6)\n  useEffect(() => {\n    const handleClickOutside = (e: MouseEvent) => {\n      const target = e.target as HTMLElement;\n\n      // Close column selector if clicking outside\n      if (showColumnSelector && !target.closest(\".column-selector-panel\")) {\n        setShowColumnSelector(false);\n      }\n\n      // Close spreadsheet options if clicking outside\n      if (showSpreadsheetOptions && !target.closest(\".spreadsheet-options-panel\")) {\n        setShowSpreadsheetOptions(false);\n      }\n\n      // Close context menu on any click\n      if (contextMenu) {\n        setContextMenu(null);\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, [showColumnSelector, showSpreadsheetOptions, contextMenu]);\n\n  const columns = useMemo<ColumnDef<DataRow>[]>(() => {\n    if (!data || !data.length || !data[0]) return [];\n    const keys = Object.keys(data[0]);\n    const cols: ColumnDef<DataRow>[] = [];\n\n    // Add row numbers column if enabled\n    if (showRowNumbers) {\n      cols.push({\n        id: \"__row_number__\",\n        accessorFn: (_, index) => index + 1,\n        minSize: 50,\n        maxSize: 70,\n        size: 60,\n        header: () => (\n          <div\n            className=\"h-7 text-xs w-full flex items-center justify-center text-muted-foreground font-medium bg-muted cursor-pointer hover:brightness-95\"\n            title=\"Click to select all\"\n          >\n            #\n          </div>\n        ),\n        cell: ({ row }) => (\n          <div className=\"text-xs text-center text-muted-foreground font-mono bg-muted cursor-pointer hover:brightness-95\">\n            {row.index + 1}\n          </div>\n        ),\n        enableColumnFilter: false,\n        enableSorting: false,\n        enableResizing: false,\n      });\n    }\n\n    const dataCols = keys\n      .map((key): ColumnDef<DataRow> => {\n        return {\n          id: key, // Explicit id to avoid TanStack Table misinterpreting numeric keys\n          accessorFn: (row) => row[key], // Use accessorFn instead of accessorKey for direct property access\n          minSize: MIN_COLUMN_WIDTH,\n          maxSize: MAX_COLUMN_WIDTH,\n          enableSorting: true,\n          header: ({ column }) => (\n            <div\n              className=\"h-7 text-xs w-full flex items-center justify-between pl-0 pr-1 cursor-pointer select-none hover:bg-muted/50\"\n              title={`${key} (click to sort)`}\n              onClick={() => column.toggleSorting()}\n            >\n              <span className=\"truncate\">{key}</span>\n              <span className=\"ml-1 flex-shrink-0\">\n                {column.getIsSorted() === \"asc\" ? (\n                  <ChevronUp className=\"h-3 w-3\" />\n                ) : column.getIsSorted() === \"desc\" ? (\n                  <ChevronDown className=\"h-3 w-3\" />\n                ) : (\n                  <ChevronsUpDown className=\"h-3 w-3 opacity-30\" />\n                )}\n              </span>\n            </div>\n          ),\n          cell: ({ row }) => {\n            const value = row.getValue(key);\n            const titleAttribute = safeStringify(value);\n            let displayValue: React.ReactNode;\n\n            if (columnRenderers && columnRenderers[key] && value !== null && value !== undefined) {\n              displayValue = columnRenderers[key](value);\n            } else if (value === null || value === undefined) {\n              displayValue = (\n                <span className=\"text-neutral-400 italic text-xs opacity-50\">null</span>\n              );\n            } else if (typeof value === \"object\" || typeof value === \"bigint\") {\n              // Properly stringify objects and BigInt for display\n              const stringifiedValue = safeStringify(value);\n              displayValue = stringifiedValue; // Use plain string for better copy behavior\n            } else {\n              displayValue = String(value); // Default to string\n            }\n\n            return (\n              <div\n                className={`p-1 px-2 align-middle text-xs overflow-hidden whitespace-nowrap select-text ${\n                  typeof value === \"object\" ? \"font-mono text-blue-500\" : \"\"\n                }`}\n                title={titleAttribute}\n              >\n                {displayValue}\n              </div>\n            );\n          },\n          enableColumnFilter: false,\n          filterFn: globalFilterFn,\n        };\n      })\n      .filter((column) => {\n        // Check both accessorKey (old) and id (new) for enabled columns\n        const accessorKey = (column as unknown as { accessorKey?: string }).accessorKey;\n        const columnId = (column as unknown as { id?: string }).id;\n        const keyToCheck = columnId || accessorKey;\n        return keyToCheck === undefined || enabledColumns[keyToCheck] !== false;\n      });\n\n    return [...cols, ...dataCols];\n  }, [data, enabledColumns, columnRenderers, showRowNumbers]);\n\n  const handleColumnSizeChange = (updater: React.SetStateAction<ColumnSizingState>) => {\n    const newSizingFromTable = typeof updater === \"function\" ? updater(columnSizing) : updater;\n    setColumnSizing(newSizingFromTable);\n    setUserResizedColumns(newSizingFromTable);\n  };\n\n  const table = useReactTable({\n    data,\n    columns,\n    columnResizeMode,\n    state: {\n      columnFilters,\n      globalFilter,\n      pagination,\n      columnSizing,\n      sorting,\n    },\n    onColumnFiltersChange: setColumnFilters,\n    onGlobalFilterChange: setGlobalFilter,\n    onPaginationChange: setPagination,\n    onColumnSizingChange: handleColumnSizeChange,\n    onSortingChange: setSorting,\n    getCoreRowModel: getCoreRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    enableGlobalFilter: true,\n    enableColumnResizing: true,\n    enableSorting: true,\n    debugTable: false,\n  });\n\n  const { rows } = table.getRowModel();\n  const ROW_HEIGHT = 32; // Must match CSS and cell content height\n\n  // Helper function to create selection range between two points\n  const createSelectionRange = (start: CellPosition, end: CellPosition): Set<string> => {\n    const selection = new Set<string>();\n    if (!data || !data[0]) return selection;\n\n    const columns = Object.keys(data[0]);\n    const minRow = Math.min(start.row, end.row);\n    const maxRow = Math.max(start.row, end.row);\n    const startColIndex = columns.indexOf(start.col);\n    const endColIndex = columns.indexOf(end.col);\n    const minColIndex = Math.min(startColIndex, endColIndex);\n    const maxColIndex = Math.max(startColIndex, endColIndex);\n\n    for (let row = minRow; row <= maxRow && row < data.length; row++) {\n      for (let colIdx = minColIndex; colIdx <= maxColIndex && colIdx < columns.length; colIdx++) {\n        const col = columns[colIdx];\n        if (col !== \"__row_number__\") {\n          selection.add(`${row}::${col}`);\n        }\n      }\n    }\n\n    return selection;\n  };\n\n  // Keyboard handlers\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      // Ctrl/Cmd + C to copy selected cells\n      if ((e.ctrlKey || e.metaKey) && e.key === \"c\" && selectedCells.size > 0) {\n        e.preventDefault();\n\n        // Get selected cells data organized by row and column\n        const cellsByPosition = new Map<string, unknown>();\n        const rowIndices = new Set<number>();\n        const columnIds = new Set<string>();\n\n        selectedCells.forEach((cellKey) => {\n          const [rowStr, colId] = cellKey.split(\"::\");\n          const rowIndex = parseInt(rowStr);\n          rowIndices.add(rowIndex);\n          columnIds.add(colId);\n\n          const row = rows[rowIndex];\n          if (row) {\n            const cell = row.getAllCells().find((c) => c.column.id === colId);\n            if (cell) {\n              cellsByPosition.set(cellKey, cell.getValue());\n            }\n          }\n        });\n\n        // Sort rows and columns\n        const sortedRows = Array.from(rowIndices).sort((a, b) => a - b);\n        const sortedCols = Array.from(columnIds);\n\n        // Build TSV string for Excel/Sheets compatibility\n        const tsvRows: string[] = [];\n        sortedRows.forEach((rowIndex) => {\n          const rowValues: string[] = [];\n          sortedCols.forEach((colId) => {\n            const cellKey = `${rowIndex}::${colId}`;\n            const value = cellsByPosition.get(cellKey);\n            if (value !== undefined) {\n              const strValue = value === null ? \"\" : safeStringify(value);\n              rowValues.push(strValue);\n            } else if (selectedCells.has(cellKey)) {\n              rowValues.push(\"\");\n            }\n          });\n          if (rowValues.length > 0) {\n            tsvRows.push(rowValues.join(\"\\t\"));\n          }\n        });\n\n        const tsvContent = tsvRows.join(\"\\n\");\n        navigator.clipboard.writeText(tsvContent).then(() => {\n          toast.success(`Copied ${selectedCells.size} cells to clipboard`);\n\n          // Visual feedback - flash selected cells\n          const tempCells = new Set(selectedCells);\n          setSelectedCells(new Set());\n          setTimeout(() => setSelectedCells(tempCells), 100);\n        });\n      }\n\n      // Ctrl/Cmd + A to select all\n      if ((e.ctrlKey || e.metaKey) && e.key === \"a\") {\n        e.preventDefault();\n        const allCells = new Set<string>();\n        rows.forEach((row, rowIndex) => {\n          row.getVisibleCells().forEach((cell) => {\n            if (cell.column.id !== \"__row_number__\") {\n              allCells.add(`${rowIndex}::${cell.column.id}`);\n            }\n          });\n        });\n        setSelectedCells(allCells);\n      }\n\n      // Escape to clear selection\n      if (e.key === \"Escape\") {\n        setSelectedCells(new Set());\n        setContextMenu(null);\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [selectedCells, rows]);\n\n  // Handle mouse up globally for drag selection\n  useEffect(() => {\n    const handleMouseUp = () => {\n      if (isDragging) {\n        setIsDragging(false);\n        if (dragEnd) {\n          setLastSelectedCell(dragEnd);\n        }\n      }\n    };\n\n    if (isDragging) {\n      document.addEventListener(\"mouseup\", handleMouseUp);\n      return () => document.removeEventListener(\"mouseup\", handleMouseUp);\n    }\n  }, [isDragging, dragEnd, setLastSelectedCell]);\n\n  // Auto-size columns when data changes\n  useEffect(() => {\n    if (data && data.length > 0 && data[0]) {\n      const newSizing: ColumnSizingState = {};\n      const keys = Object.keys(data[0]);\n\n      keys.forEach((key) => {\n        newSizing[key] = calculateOptimalWidth(data, key);\n      });\n\n      setColumnSizing(newSizing);\n      setUserResizedColumns(newSizing);\n    }\n  }, [data]); // Trigger when data changes\n\n  const rowVirtualizer = useVirtualizer({\n    count: rows.length,\n    estimateSize: () => ROW_HEIGHT,\n    getScrollElement: () => horizontalScrollRef.current,\n    overscan: 10, // Lowered slightly, adjust as needed\n  });\n\n  const virtualRows = rowVirtualizer.getVirtualItems();\n  const paddingTop = virtualRows.length > 0 ? (virtualRows[0]?.start ?? 0) : 0;\n  const paddingBottom =\n    virtualRows.length > 0\n      ? rowVirtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0)\n      : 0;\n\n  const exportToCSV = () => {\n    if (!data || !data.length) return;\n    const visibleKeys = table.getVisibleLeafColumns().map((c) => c.id);\n    const headers = visibleKeys.length > 0 ? visibleKeys : data[0] ? Object.keys(data[0]) : [];\n    if (headers.length === 0) return;\n\n    const csvRows = [headers.join(\",\")];\n    // Export based on current filter, but original data (not paginated)\n    const dataToExport = table.getFilteredRowModel().rows.map((r) => r.original);\n\n    for (const row of dataToExport) {\n      const values = headers.map((header) => {\n        const value = row[header];\n        if (value === null || value === undefined) return \"\";\n        if (typeof value === \"string\" && value.includes(\",\"))\n          return `\"${value.replace(/\"/g, '\"\"')}\"`;\n        if (typeof value === \"object\" || typeof value === \"bigint\")\n          return `\"${safeStringify(value).replace(/\"/g, '\"\"')}\"`;\n        return String(value);\n      });\n      csvRows.push(values.join(\",\"));\n    }\n    const blob = new Blob([csvRows.join(\"\\n\")], {\n      type: \"text/csv;charset=utf-8;\",\n    });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement(\"a\");\n    link.setAttribute(\"href\", url);\n    link.setAttribute(\"download\", `duck-ui-export-${new Date().toISOString().slice(0, 19)}.csv`);\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n    URL.revokeObjectURL(url);\n    toast.success(\"Exported to CSV\");\n  };\n\n  const exportToJSON = () => {\n    if (!data || !data.length) return;\n\n    const dataToExport = table.getFilteredRowModel().rows.map((r) => r.original);\n\n    const jsonString = JSON.stringify(\n      dataToExport,\n      (_, v) => (typeof v === \"bigint\" ? v.toString() : v),\n      2\n    );\n\n    const blob = new Blob([jsonString], { type: \"application/json\" });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement(\"a\");\n    link.setAttribute(\"href\", url);\n    link.setAttribute(\"download\", `duck-ui-export-${new Date().toISOString().slice(0, 19)}.json`);\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n    URL.revokeObjectURL(url);\n\n    toast.success(\"Exported to JSON\");\n  };\n\n  const exportToXLSX = async () => {\n    if (!data || !data.length) return;\n\n    try {\n      // Dynamic import - only load ExcelJS when needed\n      const ExcelJS = await import(\"exceljs\");\n\n      const dataToExport = table.getFilteredRowModel().rows.map((r) => {\n        const row: Record<string, unknown> = {};\n        Object.keys(r.original).forEach((key) => {\n          const val = r.original[key];\n          row[key] = typeof val === \"bigint\" ? val.toString() : val;\n        });\n        return row;\n      });\n\n      const workbook = new ExcelJS.Workbook();\n      const worksheet = workbook.addWorksheet(\"Data\");\n\n      const keys = Object.keys(dataToExport[0] || {});\n      const maxWidth = 50;\n      worksheet.columns = keys.map((key) => ({\n        header: key,\n        key,\n        width: Math.min(\n          maxWidth,\n          Math.max(\n            key.length,\n            ...dataToExport.slice(0, 100).map((row) => String(row[key] || \"\").length)\n          )\n        ),\n      }));\n\n      worksheet.addRows(dataToExport);\n\n      const buffer = await workbook.xlsx.writeBuffer();\n      const blob = new Blob([buffer], {\n        type: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n      });\n      const url = URL.createObjectURL(blob);\n      const link = document.createElement(\"a\");\n      link.setAttribute(\"href\", url);\n      link.setAttribute(\"download\", `duck-ui-export-${new Date().toISOString().slice(0, 19)}.xlsx`);\n      document.body.appendChild(link);\n      link.click();\n      document.body.removeChild(link);\n      URL.revokeObjectURL(url);\n\n      toast.success(\"Exported to Excel\");\n    } catch (error) {\n      console.error(\"XLSX export failed:\", error);\n      toast.error(\"Failed to export to Excel\");\n    }\n  };\n\n  const exportToDuckDB = async () => {\n    if (!data || !data.length) return;\n\n    try {\n      toast.info(\"Preparing DuckDB export...\");\n\n      const dataToExport = table.getFilteredRowModel().rows.map((r) => r.original);\n\n      // Get current connection from store\n      const { connection, db } = useDuckStore.getState();\n      if (!connection || !db) {\n        throw new Error(\"Database not initialized\");\n      }\n\n      // Use Parquet format (DuckDB native format)\n      const fileName = `export_${new Date().toISOString().slice(0, 19)}.parquet`;\n\n      // Create VALUES clause for the data\n      const createValuesClause = () => {\n        if (dataToExport.length === 0) return \"\";\n\n        const keys = Object.keys(dataToExport[0]);\n        const values = dataToExport.map((row) => {\n          const rowValues = keys.map((key) => {\n            const val = row[key];\n            if (val === null || val === undefined) return \"NULL\";\n            if (typeof val === \"string\") return `'${val.replace(/'/g, \"''\")}'`;\n            if (typeof val === \"bigint\") return val.toString();\n            if (typeof val === \"object\") return `'${JSON.stringify(val).replace(/'/g, \"''\")}'`;\n            return String(val);\n          });\n          return `(${rowValues.join(\", \")})`;\n        });\n\n        return values.join(\", \");\n      };\n\n      const valuesClause = createValuesClause();\n      const keys = Object.keys(dataToExport[0]);\n\n      await connection.query(\n        `COPY (SELECT * FROM (VALUES ${valuesClause}) AS t(${keys.map((k) => `\"${k}\"`).join(\", \")})) TO '${fileName}' (FORMAT 'parquet')`\n      );\n\n      const buffer = await db.copyFileToBuffer(fileName);\n      await db.dropFile(fileName);\n\n      // Convert Uint8Array to ArrayBuffer for Blob compatibility\n      const arrayBuffer = buffer.buffer.slice(0) as ArrayBuffer;\n      const blob = new Blob([arrayBuffer], { type: \"application/octet-stream\" });\n      const url = URL.createObjectURL(blob);\n      const link = document.createElement(\"a\");\n      link.setAttribute(\"href\", url);\n      link.setAttribute(\"download\", fileName);\n      document.body.appendChild(link);\n      link.click();\n      document.body.removeChild(link);\n      URL.revokeObjectURL(url);\n\n      toast.success(\"Exported to Parquet (DuckDB format)\");\n    } catch (error) {\n      console.error(\"DuckDB export failed:\", error);\n      toast.error(\n        \"Failed to export: \" + (error instanceof Error ? error.message : \"Unknown error\")\n      );\n    }\n  };\n\n  // Save to folder functions\n  const saveToFolderAsCSV = async (folderId: string, folderName: string) => {\n    if (!data || !data.length) return;\n\n    try {\n      const visibleKeys = table.getVisibleLeafColumns().map((c) => c.id);\n      const headers = visibleKeys.length > 0 ? visibleKeys : data[0] ? Object.keys(data[0]) : [];\n      if (headers.length === 0) return;\n\n      const csvRows = [headers.join(\",\")];\n      const dataToExport = table.getFilteredRowModel().rows.map((r) => r.original);\n\n      for (const row of dataToExport) {\n        const values = headers.map((header) => {\n          const value = row[header];\n          if (value === null || value === undefined) return \"\";\n          if (typeof value === \"string\" && value.includes(\",\"))\n            return `\"${value.replace(/\"/g, '\"\"')}\"`;\n          if (typeof value === \"object\" || typeof value === \"bigint\")\n            return `\"${safeStringify(value).replace(/\"/g, '\"\"')}\"`;\n          return String(value);\n        });\n        csvRows.push(values.join(\",\"));\n      }\n\n      const content = csvRows.join(\"\\n\");\n      const fileName = `export-${new Date().toISOString().slice(0, 19).replace(/:/g, \"-\")}.csv`;\n\n      await fileSystemService.saveFile(folderId, fileName, content);\n      toast.success(`Saved to ${folderName}/${fileName}`);\n    } catch (error) {\n      console.error(\"Save to folder failed:\", error);\n      toast.error(\"Failed to save: \" + (error instanceof Error ? error.message : \"Unknown error\"));\n    }\n  };\n\n  const saveToFolderAsJSON = async (folderId: string, folderName: string) => {\n    if (!data || !data.length) return;\n\n    try {\n      const dataToExport = table.getFilteredRowModel().rows.map((r) => r.original);\n      const jsonString = JSON.stringify(\n        dataToExport,\n        (_, v) => (typeof v === \"bigint\" ? v.toString() : v),\n        2\n      );\n\n      const fileName = `export-${new Date().toISOString().slice(0, 19).replace(/:/g, \"-\")}.json`;\n      await fileSystemService.saveFile(folderId, fileName, jsonString);\n      toast.success(`Saved to ${folderName}/${fileName}`);\n    } catch (error) {\n      console.error(\"Save to folder failed:\", error);\n      toast.error(\"Failed to save: \" + (error instanceof Error ? error.message : \"Unknown error\"));\n    }\n  };\n\n  const saveToFolderAsXLSX = async (folderId: string, folderName: string) => {\n    if (!data || !data.length) return;\n\n    try {\n      const ExcelJS = await import(\"exceljs\");\n      const dataToExport = table.getFilteredRowModel().rows.map((r) => {\n        const row: Record<string, unknown> = {};\n        Object.keys(r.original).forEach((key) => {\n          const val = r.original[key];\n          row[key] = typeof val === \"bigint\" ? val.toString() : val;\n        });\n        return row;\n      });\n\n      const workbook = new ExcelJS.Workbook();\n      const worksheet = workbook.addWorksheet(\"Data\");\n\n      const keys = Object.keys(dataToExport[0] || {});\n      const maxWidth = 50;\n      worksheet.columns = keys.map((key) => ({\n        header: key,\n        key,\n        width: Math.min(\n          maxWidth,\n          Math.max(\n            key.length,\n            ...dataToExport.slice(0, 100).map((row) => String(row[key] || \"\").length)\n          )\n        ),\n      }));\n\n      worksheet.addRows(dataToExport);\n\n      const xlsxBuffer = await workbook.xlsx.writeBuffer();\n      const fileName = `export-${new Date().toISOString().slice(0, 19).replace(/:/g, \"-\")}.xlsx`;\n\n      await fileSystemService.saveFile(folderId, fileName, xlsxBuffer);\n      toast.success(`Saved to ${folderName}/${fileName}`);\n    } catch (error) {\n      console.error(\"Save to folder failed:\", error);\n      toast.error(\"Failed to save: \" + (error instanceof Error ? error.message : \"Unknown error\"));\n    }\n  };\n\n  const saveToFolderAsParquet = async (folderId: string, folderName: string) => {\n    if (!data || !data.length) return;\n\n    try {\n      const dataToExport = table.getFilteredRowModel().rows.map((r) => r.original);\n      const { connection, db } = useDuckStore.getState();\n\n      if (!connection || !db) {\n        throw new Error(\"Database not initialized\");\n      }\n\n      const tempFileName = `temp_export_${Date.now()}.parquet`;\n\n      const createValuesClause = () => {\n        if (dataToExport.length === 0) return \"\";\n        const keys = Object.keys(dataToExport[0]);\n        const values = dataToExport.map((row) => {\n          const rowValues = keys.map((key) => {\n            const val = row[key];\n            if (val === null || val === undefined) return \"NULL\";\n            if (typeof val === \"string\") return `'${val.replace(/'/g, \"''\")}'`;\n            if (typeof val === \"bigint\") return val.toString();\n            if (typeof val === \"object\") return `'${JSON.stringify(val).replace(/'/g, \"''\")}'`;\n            return String(val);\n          });\n          return `(${rowValues.join(\", \")})`;\n        });\n        return values.join(\", \");\n      };\n\n      const valuesClause = createValuesClause();\n      const keys = Object.keys(dataToExport[0]);\n\n      await connection.query(\n        `COPY (SELECT * FROM (VALUES ${valuesClause}) AS t(${keys.map((k) => `\"${k}\"`).join(\", \")})) TO '${tempFileName}' (FORMAT 'parquet')`\n      );\n\n      const buffer = await db.copyFileToBuffer(tempFileName);\n      await db.dropFile(tempFileName);\n\n      const fileName = `export-${new Date().toISOString().slice(0, 19).replace(/:/g, \"-\")}.parquet`;\n      // Create new ArrayBuffer copy to satisfy TypeScript\n      const arrayBuffer = new ArrayBuffer(buffer.byteLength);\n      new Uint8Array(arrayBuffer).set(buffer);\n      await fileSystemService.saveFile(folderId, fileName, arrayBuffer);\n      toast.success(`Saved to ${folderName}/${fileName}`);\n    } catch (error) {\n      console.error(\"Save to folder failed:\", error);\n      toast.error(\"Failed to save: \" + (error instanceof Error ? error.message : \"Unknown error\"));\n    }\n  };\n\n  const toggleColumnVisibility = (columnId: string) => {\n    setEnabledColumns((prev) => ({ ...prev, [columnId]: !prev[columnId] }));\n  };\n\n  const toggleAllColumns = (value: boolean) => {\n    if (!data || !data.length || !data[0]) return;\n    setEnabledColumns(\n      Object.keys(data[0]).reduce(\n        (acc, key) => {\n          acc[key] = value;\n          return acc;\n        },\n        {} as Record<string, boolean>\n      )\n    );\n  };\n\n  const ColumnSelector = () => {\n    const allColumnKeys = data?.[0] ? Object.keys(data[0]) : [];\n\n    // Memoize column keys to prevent re-renders\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    const stableColumnKeys = useMemo(() => allColumnKeys, [JSON.stringify(allColumnKeys)]);\n\n    // Simplified filtering for column selector\n    const filteredColumnKeys = useMemo(() => {\n      if (!columnSelectorFilter) return stableColumnKeys;\n      return stableColumnKeys.filter((key) =>\n        key.toLowerCase().includes(columnSelectorFilter.toLowerCase())\n      );\n    }, [stableColumnKeys, columnSelectorFilter]);\n\n    if (!data || !data.length || !data[0]) return null;\n    const visibleCount = Object.values(enabledColumns).filter(Boolean).length;\n    const totalCount = allColumnKeys.length;\n\n    return (\n      <Card className=\"column-selector-panel absolute right-0 top-12 z-20 w-[350px] bg-background shadow-lg rounded-md border p-2\">\n        <CardContent className=\"p-2\">\n          <div className=\"flex justify-between items-center mb-2\">\n            <h3 className=\"text-sm font-semibold\">\n              Toggle Columns{\" \"}\n              <span className=\"text-xs text-muted-foreground\">\n                ({visibleCount}/{totalCount})\n              </span>\n            </h3>\n            <div className=\"flex gap-1\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"h-7 px-1.5 text-xs\"\n                onClick={() => toggleAllColumns(true)}\n              >\n                All\n              </Button>\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"h-7 px-1.5 text-xs\"\n                onClick={() => toggleAllColumns(false)}\n              >\n                None\n              </Button>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"h-7 w-7 p-0\"\n                onClick={() => setShowColumnSelector(false)}\n              >\n                <X className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          </div>\n          <div className=\"relative mb-3\">\n            <Search className=\"absolute left-2 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground\" />\n            <Input\n              value={columnSelectorFilter}\n              onChange={(e) => setColumnSelectorFilter(e.target.value)}\n              placeholder=\"Filter columns...\"\n              className=\"pl-7 h-7 text-xs w-full\"\n            />\n            {columnSelectorFilter && (\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"absolute right-1 top-1/2 transform -translate-y-1/2 h-5 w-5 p-0\"\n                onClick={() => setColumnSelectorFilter(\"\")}\n              >\n                <X className=\"h-3 w-3\" />\n              </Button>\n            )}\n          </div>\n          <div className=\"h-[350px] pr-1 overflow-y-auto space-y-1\">\n            {filteredColumnKeys.map((columnId) => (\n              <div key={columnId} className=\"flex items-center space-x-2 pl-1\">\n                <Checkbox\n                  id={`column-sel-${columnId}`}\n                  checked={enabledColumns[columnId] !== false}\n                  onCheckedChange={() => toggleColumnVisibility(columnId)}\n                />\n                <label\n                  htmlFor={`column-sel-${columnId}`}\n                  className=\"text-xs cursor-pointer truncate max-w-[240px]\"\n                  title={columnId}\n                >\n                  {columnId}\n                </label>\n              </div>\n            ))}\n            {filteredColumnKeys.length === 0 && (\n              <div className=\"text-xs text-muted-foreground text-center py-2\">\n                No columns match your filter.\n              </div>\n            )}\n          </div>\n        </CardContent>\n      </Card>\n    );\n  };\n\n  const SpreadsheetOptions = () => {\n    return (\n      <Card className=\"spreadsheet-options-panel absolute right-0 top-12 z-20 w-[300px] bg-background shadow-lg rounded-md border p-2\">\n        <CardContent className=\"p-2\">\n          <div className=\"flex justify-between items-center mb-3\">\n            <h3 className=\"text-sm font-semibold\">Spreadsheet Options</h3>\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              className=\"h-7 w-7 p-0\"\n              onClick={() => setShowSpreadsheetOptions(false)}\n            >\n              <X className=\"h-4 w-4\" />\n            </Button>\n          </div>\n          <div className=\"space-y-3\">\n            <div className=\"flex items-center space-x-2\">\n              <Checkbox\n                id=\"show-row-numbers\"\n                checked={showRowNumbers}\n                onCheckedChange={(checked) => setShowRowNumbers(checked === true)}\n              />\n              <label htmlFor=\"show-row-numbers\" className=\"text-xs cursor-pointer\">\n                Show row numbers\n              </label>\n            </div>\n            <div className=\"flex items-center space-x-2\">\n              <Checkbox\n                id=\"zebra-stripes\"\n                checked={zebraStripes}\n                onCheckedChange={(checked) => setZebraStripes(checked === true)}\n              />\n              <label htmlFor=\"zebra-stripes\" className=\"text-xs cursor-pointer\">\n                Zebra stripes\n              </label>\n            </div>\n            <div className=\"flex items-center space-x-2\">\n              <Checkbox\n                id=\"show-grid-lines\"\n                checked={showGridLines}\n                onCheckedChange={(checked) => setShowGridLines(checked === true)}\n              />\n              <label htmlFor=\"show-grid-lines\" className=\"text-xs cursor-pointer\">\n                Show grid lines\n              </label>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n    );\n  };\n\n  const ContextMenu = () => {\n    if (!contextMenu) return null;\n\n    return (\n      <div\n        className=\"context-menu fixed z-20 bg-background border border-border rounded-md shadow-lg py-1 min-w-[160px]\"\n        style={{ left: contextMenu.x, top: contextMenu.y }}\n      >\n        <button\n          className=\"w-full text-left px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground flex items-center gap-2\"\n          onClick={() => {\n            // Trigger copy\n            const e = new KeyboardEvent(\"keydown\", { key: \"c\", ctrlKey: true });\n            window.dispatchEvent(e);\n            setContextMenu(null);\n          }}\n        >\n          <Copy className=\"h-3 w-3\" />\n          Copy\n        </button>\n        <button\n          className=\"w-full text-left px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground flex items-center gap-2\"\n          onClick={() => {\n            // Select all\n            const e = new KeyboardEvent(\"keydown\", { key: \"a\", ctrlKey: true });\n            window.dispatchEvent(e);\n            setContextMenu(null);\n          }}\n        >\n          <MousePointer className=\"h-3 w-3\" />\n          Select All\n        </button>\n        <button\n          className=\"w-full text-left px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground flex items-center gap-2\"\n          onClick={() => {\n            // Select row\n            if (selectedCells.size > 0) {\n              const firstCell = Array.from(selectedCells)[0];\n              const rowIndex = parseInt(firstCell.split(\"::\")[0]);\n              if (data[0]) {\n                const rowCells = new Set<string>();\n                Object.keys(data[0]).forEach((key) => {\n                  if (key !== \"__row_number__\") {\n                    rowCells.add(`${rowIndex}::${key}`);\n                  }\n                });\n                setSelectedCells(rowCells);\n              }\n            }\n            setContextMenu(null);\n          }}\n        >\n          <MoreHorizontal className=\"h-3 w-3\" />\n          Select Row\n        </button>\n        <button\n          className=\"w-full text-left px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground flex items-center gap-2\"\n          onClick={() => {\n            // Select column\n            if (selectedCells.size > 0) {\n              const firstCell = Array.from(selectedCells)[0];\n              const colId = firstCell.split(\"::\")[1];\n              if (colId) {\n                const colCells = new Set<string>();\n                for (let i = 0; i < data.length; i++) {\n                  colCells.add(`${i}::${colId}`);\n                }\n                setSelectedCells(colCells);\n              }\n            }\n            setContextMenu(null);\n          }}\n        >\n          <MoreHorizontal className=\"h-3 w-3 rotate-90\" />\n          Select Column\n        </button>\n        <hr className=\"my-1 border-border\" />\n        <button\n          className=\"w-full text-left px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground flex items-center gap-2\"\n          onClick={() => {\n            exportToCSV();\n            setContextMenu(null);\n          }}\n        >\n          <Download className=\"h-3 w-3\" />\n          Export...\n        </button>\n      </div>\n    );\n  };\n\n  if (!data || data.length === 0) {\n    return (\n      <div className=\"flex justify-center items-center h-32 text-muted-foreground\">\n        No data available.\n      </div>\n    );\n  }\n\n  return (\n    <TooltipProvider>\n      <div className=\"flex flex-col h-full w-full min-w-0 overflow-hidden\" ref={tableContainerRef}>\n        {/* Top controls area */}\n        <div className=\"flex flex-col sm:flex-row sm:items-center justify-between mb-4 gap-2 flex-shrink-0 mt-2 px-2\">\n          <div className=\"flex items-center gap-2 flex-1\">\n            <div className=\"relative flex-1 max-w-md\">\n              <Search className=\"absolute left-2 top-1/2 transform -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground\" />\n              <Input\n                placeholder=\"Search all columns...\"\n                value={globalFilterInput}\n                onChange={(e) => setGlobalFilterInput(e.target.value)}\n                className=\"pl-8 h-8 text-xs\"\n              />\n            </div>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => {\n                setGlobalFilterInput(\"\");\n                table.resetColumnFilters(true);\n              }}\n              className=\"h-8 text-xs\"\n              disabled={!globalFilterInput && !table.getState().columnFilters.length}\n            >\n              Clear Filters\n            </Button>\n          </div>\n          <div className=\"hidden md:flex items-center gap-x-4 text-xs text-muted-foreground px-2\">\n            {executionTime !== null && executionTime !== undefined && (\n              <div className=\"flex items-center gap-1\" title=\"Query execution time\">\n                <Clock className=\"h-3 w-3\" />\n                <span>{formatDuration(executionTime)}</span>\n              </div>\n            )}\n            {responseSize !== null && responseSize !== undefined && (\n              <div className=\"flex items-center\" title=\"Response size\">\n                <span>{formatBytes(responseSize)}</span>\n              </div>\n            )}\n            <span title=\"Showing rows count\">{data.length.toLocaleString()} rows</span>\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => {\n                setShowStatsPanel(!showStatsPanel);\n                if (!showStatsPanel) setStatsPanelMinimized(false);\n              }}\n              className=\"h-8 text-xs\"\n              title=\"Show column statistics\"\n            >\n              <BarChart3 className=\"h-3.5 w-3.5 mr-1\" />\n              Stats\n            </Button>\n            <div className=\"relative\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={() => setShowColumnSelector(!showColumnSelector)}\n                className=\"h-8 text-xs\"\n                title=\"Configure visible columns\"\n              >\n                <SlidersHorizontal className=\"h-3.5 w-3.5 mr-1\" />\n                Columns\n              </Button>\n              {showColumnSelector && <ColumnSelector />}\n            </div>\n            <div className=\"relative\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={() => setShowSpreadsheetOptions(!showSpreadsheetOptions)}\n                className=\"h-8 text-xs\"\n                title=\"Spreadsheet display options\"\n              >\n                <Grid3X3 className=\"h-3.5 w-3.5 mr-1\" />\n                View\n              </Button>\n              {showSpreadsheetOptions && <SpreadsheetOptions />}\n            </div>\n            {Object.keys(userResizedColumns).length > 0 && (\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                onClick={() => setUserResizedColumns({})}\n                className=\"h-8 text-xs\"\n                title=\"Reset column widths\"\n              >\n                Reset Size\n              </Button>\n            )}\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <Button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  className=\"h-8 text-xs\"\n                  disabled={!data || !data.length}\n                  title=\"Export data in various formats\"\n                >\n                  <Download className=\"mr-1 h-3.5 w-3.5\" />\n                  Export\n                  <ChevronDown className=\"ml-1 h-3 w-3\" />\n                </Button>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\">\n                <DropdownMenuItem onClick={exportToCSV}>\n                  <Download className=\"mr-2 h-3.5 w-3.5\" />\n                  Export as CSV\n                </DropdownMenuItem>\n                <DropdownMenuItem onClick={exportToJSON}>\n                  <Download className=\"mr-2 h-3.5 w-3.5\" />\n                  Export as JSON\n                </DropdownMenuItem>\n                <DropdownMenuItem onClick={exportToXLSX}>\n                  <Download className=\"mr-2 h-3.5 w-3.5\" />\n                  Export as Excel (XLSX)\n                </DropdownMenuItem>\n                <DropdownMenuItem onClick={exportToDuckDB}>\n                  <Download className=\"mr-2 h-3.5 w-3.5\" />\n                  Export as Parquet\n                </DropdownMenuItem>\n\n                {/* Save to Folder submenu */}\n                {mountedFolders.length > 0 && (\n                  <>\n                    <DropdownMenuSeparator />\n                    <DropdownMenuSub>\n                      <DropdownMenuSubTrigger>\n                        <FolderOpen className=\"mr-2 h-3.5 w-3.5\" />\n                        Save to Folder\n                      </DropdownMenuSubTrigger>\n                      <DropdownMenuSubContent>\n                        {mountedFolders.map((folder) => (\n                          <DropdownMenuSub key={folder.id}>\n                            <DropdownMenuSubTrigger>\n                              <FolderOpen className=\"mr-2 h-3.5 w-3.5\" />\n                              {folder.name}\n                            </DropdownMenuSubTrigger>\n                            <DropdownMenuSubContent>\n                              <DropdownMenuItem\n                                onClick={() => saveToFolderAsCSV(folder.id, folder.name)}\n                              >\n                                <FileText className=\"mr-2 h-3.5 w-3.5\" />\n                                Save as CSV\n                              </DropdownMenuItem>\n                              <DropdownMenuItem\n                                onClick={() => saveToFolderAsJSON(folder.id, folder.name)}\n                              >\n                                <FileJson className=\"mr-2 h-3.5 w-3.5\" />\n                                Save as JSON\n                              </DropdownMenuItem>\n                              <DropdownMenuItem\n                                onClick={() => saveToFolderAsXLSX(folder.id, folder.name)}\n                              >\n                                <FileSpreadsheet className=\"mr-2 h-3.5 w-3.5\" />\n                                Save as Excel (XLSX)\n                              </DropdownMenuItem>\n                              <DropdownMenuItem\n                                onClick={() => saveToFolderAsParquet(folder.id, folder.name)}\n                              >\n                                <FileArchive className=\"mr-2 h-3.5 w-3.5\" />\n                                Save as Parquet\n                              </DropdownMenuItem>\n                            </DropdownMenuSubContent>\n                          </DropdownMenuSub>\n                        ))}\n                      </DropdownMenuSubContent>\n                    </DropdownMenuSub>\n                  </>\n                )}\n              </DropdownMenuContent>\n            </DropdownMenu>\n\n            {/* Selection indicator */}\n            {selectedCells.size > 0 && (\n              <div className=\"flex items-center gap-2 ml-4 px-3 py-1 bg-primary/10 rounded-md border\">\n                <span className=\"text-xs text-muted-foreground\">\n                  {selectedCells.size} cell{selectedCells.size !== 1 ? \"s\" : \"\"} selected\n                </span>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={() => setSelectedCells(new Set())}\n                  className=\"h-6 px-2 text-xs hover:bg-primary/20\"\n                >\n                  Clear\n                </Button>\n              </div>\n            )}\n          </div>\n        </div>\n\n        {/* Main table area */}\n        <div\n          className={`flex-1 rounded-md bg-card relative overflow-hidden ${\n            showGridLines ? \"border-2\" : \"border\"\n          } border-border`}\n        >\n          <div\n            ref={horizontalScrollRef}\n            className=\"h-full w-full overflow-auto\"\n            style={{\n              height: typeof tableHeight === \"string\" ? tableHeight : `${tableHeight}px`,\n              maxWidth: \"100%\",\n            }}\n          >\n            <div\n              style={{\n                width: table.getTotalSize(),\n                minWidth: \"100%\",\n                maxWidth: \"max-content\",\n                position: \"relative\",\n              }}\n            >\n              <table\n                className={`w-full border-collapse table-fixed ${\n                  showGridLines ? \"border-spacing-0\" : \"\"\n                }`}\n              >\n                <thead\n                  className={`sticky top-0 z-10 bg-muted/70 backdrop-blur-sm ${\n                    showGridLines ? \"border-b-2 border-border\" : \"shadow-sm\"\n                  }`}\n                >\n                  {table.getHeaderGroups().map((headerGroup) => (\n                    <tr key={headerGroup.id} className=\"border-b border-border/40\">\n                      {headerGroup.headers.map((header) => (\n                        <th\n                          key={header.id}\n                          className={`h-9 px-0 text-left align-middle font-medium text-muted-foreground whitespace-nowrap text-xs relative select-none ${\n                            showGridLines ? \"border-r border-border/50\" : \"\"\n                          }`}\n                          style={{\n                            width: header.getSize(),\n                            minWidth: header.column.columnDef.minSize, // From columnDef\n                            maxWidth: header.column.columnDef.maxSize, // From columnDef\n                          }}\n                        >\n                          <div className=\"flex items-center justify-between w-full h-full px-2\">\n                            {flexRender(header.column.columnDef.header, header.getContext())}\n                          </div>\n                          {header.column.getCanResize() && (\n                            <div\n                              className={`absolute right-0 top-0 h-full w-2 cursor-col-resize select-none touch-none group ${\n                                header.column.getIsResizing() ? \"bg-primary/20\" : \"\"\n                              }`}\n                              onMouseDown={header.getResizeHandler()}\n                              onTouchStart={header.getResizeHandler()}\n                              style={{ userSelect: \"none\" }}\n                            >\n                              <div\n                                className={`w-[1px] h-4/6 my-auto ${\n                                  header.column.getIsResizing()\n                                    ? \"bg-primary w-[2px]\"\n                                    : \"bg-border/60 group-hover:bg-primary/60 group-hover:w-[2px]\"\n                                }`}\n                              />\n                            </div>\n                          )}\n                        </th>\n                      ))}\n                    </tr>\n                  ))}\n                </thead>\n                <tbody\n                  className=\"bg-card text-card-foreground relative\"\n                  style={{ userSelect: isDragging ? \"none\" : \"auto\" }}\n                >\n                  {paddingTop > 0 && (\n                    <tr style={{ height: `${paddingTop}px` }}>\n                      <td colSpan={table.getVisibleLeafColumns().length} />\n                    </tr>\n                  )}\n                  {virtualRows.map((virtualRow) => {\n                    const row = rows[virtualRow.index];\n                    if (!row) return null;\n                    return (\n                      <tr\n                        key={virtualRow.key}\n                        data-index={virtualRow.index}\n                        className={`transition-colors ${\n                          zebraStripes && virtualRow.index % 2 === 0\n                            ? \"hover:bg-muted/40\" // Stronger hover for zebra striped rows\n                            : \"hover:bg-muted/20\" // Subtle hover for regular rows\n                        } ${row.getIsSelected() ? \"bg-muted\" : \"\"} ${\n                          zebraStripes && virtualRow.index % 2 === 0\n                            ? \"bg-muted/20\"\n                            : \"bg-background\"\n                        } ${showGridLines ? \"border-b border-border/30\" : \"\"}`}\n                        style={{ height: `${ROW_HEIGHT}px` }} // Use fixed ROW_HEIGHT\n                      >\n                        {row.getVisibleCells().map((cell) => {\n                          const cellKey = `${virtualRow.index}::${cell.column.id}`;\n                          const isSelected = selectedCells.has(cellKey);\n\n                          return (\n                            <td\n                              key={cell.id}\n                              className={`align-middle overflow-hidden relative ${\n                                showGridLines ? \"border-r border-border/30\" : \"\"\n                              } ${isSelected ? \"bg-primary/20 ring-1 ring-primary/30\" : \"\"} ${\n                                isDragging ? \"cursor-crosshair\" : \"cursor-cell\"\n                              }`}\n                              style={{\n                                width: cell.column.getSize(),\n                                minWidth: cell.column.columnDef.minSize,\n                                maxWidth: cell.column.columnDef.maxSize,\n                              }}\n                              onMouseDown={(e) => {\n                                if (cell.column.id !== \"__row_number__\" && e.button === 0) {\n                                  e.preventDefault();\n                                  const currentPosition = {\n                                    row: virtualRow.index,\n                                    col: cell.column.id,\n                                  };\n\n                                  if (e.shiftKey && lastSelectedCell) {\n                                    // Shift+click: select rectangular range using helper function\n                                    const rangeSelection = createSelectionRange(\n                                      lastSelectedCell,\n                                      currentPosition\n                                    );\n                                    setSelectedCells(rangeSelection);\n                                  } else if (e.ctrlKey || e.metaKey) {\n                                    // Ctrl/Cmd+click: toggle cell in selection\n                                    const newSelection = new Set(selectedCells);\n                                    if (newSelection.has(cellKey)) {\n                                      newSelection.delete(cellKey);\n                                    } else {\n                                      newSelection.add(cellKey);\n                                    }\n                                    setSelectedCells(newSelection);\n                                    setLastSelectedCell(currentPosition);\n                                  } else {\n                                    // Regular click: select only this cell and start drag\n                                    setSelectedCells(new Set([cellKey]));\n                                    setLastSelectedCell(currentPosition);\n\n                                    // Start drag selection\n                                    setIsDragging(true);\n                                    setDragStart(currentPosition);\n                                    setDragEnd(currentPosition);\n                                  }\n                                }\n                              }}\n                              onMouseEnter={() => {\n                                if (\n                                  isDragging &&\n                                  dragStart &&\n                                  cell.column.id !== \"__row_number__\"\n                                ) {\n                                  const currentPosition = {\n                                    row: virtualRow.index,\n                                    col: cell.column.id,\n                                  };\n                                  setDragEnd(currentPosition);\n\n                                  // Update selection during drag\n                                  const dragSelection = createSelectionRange(\n                                    dragStart,\n                                    currentPosition\n                                  );\n                                  setSelectedCells(dragSelection);\n                                }\n                              }}\n                              onContextMenu={(e) => {\n                                if (cell.column.id !== \"__row_number__\") {\n                                  e.preventDefault();\n                                  setContextMenu({\n                                    x: e.clientX,\n                                    y: e.clientY,\n                                  });\n                                  if (!selectedCells.has(cellKey)) {\n                                    setSelectedCells(new Set([cellKey]));\n                                  }\n                                }\n                              }}\n                              onDoubleClick={() => {\n                                if (cell.column.id !== \"__row_number__\") {\n                                  const cellValue = cell.getValue();\n                                  setViewedCell({\n                                    value: cellValue,\n                                    columnName: cell.column.id,\n                                    rowIndex: virtualRow.index,\n                                  });\n                                }\n                              }}\n                            >\n                              {flexRender(cell.column.columnDef.cell, cell.getContext())}\n                            </td>\n                          );\n                        })}\n                      </tr>\n                    );\n                  })}\n                  {paddingBottom > 0 && (\n                    <tr style={{ height: `${paddingBottom}px` }}>\n                      <td colSpan={table.getVisibleFlatColumns().length} />\n                    </tr>\n                  )}\n                  {rows.length === 0 && (\n                    <tr>\n                      <td\n                        colSpan={columns.length || 1}\n                        className=\"h-24 text-center text-muted-foreground\"\n                      >\n                        No results match your filters.\n                      </td>\n                    </tr>\n                  )}\n                </tbody>\n              </table>\n            </div>\n          </div>\n        </div>\n\n        {/* Pagination controls */}\n        <div className=\"flex items-center justify-between mt-4 flex-shrink-0 mb-2 px-4\">\n          <div className=\"flex items-center gap-2\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => table.setPageIndex(0)}\n              disabled={!table.getCanPreviousPage()}\n              className=\"h-7 w-7 p-0 text-xs\"\n              title=\"First page\"\n            >\n              ««\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => table.previousPage()}\n              disabled={!table.getCanPreviousPage()}\n              className=\"h-7 w-7 p-0 text-xs\"\n              title=\"Previous page\"\n            >\n              «\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => table.nextPage()}\n              disabled={!table.getCanNextPage()}\n              className=\"h-7 w-7 p-0 text-xs\"\n              title=\"Next page\"\n            >\n              »\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => table.setPageIndex(table.getPageCount() - 1)}\n              disabled={!table.getCanNextPage()}\n              className=\"h-7 w-7 p-0 text-xs\"\n              title=\"Last page\"\n            >\n              »»\n            </Button>\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <span className=\"text-xs text-muted-foreground\">\n              Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}\n            </span>\n            <select\n              value={table.getState().pagination.pageSize}\n              onChange={(e) => {\n                table.setPageSize(Number(e.target.value));\n              }}\n              className=\"text-xs border border-border/40 rounded-md h-7 px-2 bg-background\"\n              title=\"Rows per page\"\n            >\n              {[10, 25, 50, 100, 200, 500, 1000, 5000, 10000].map((pageSize) => (\n                <option key={pageSize} value={pageSize}>\n                  {pageSize} rows\n                </option>\n              ))}\n            </select>\n          </div>\n        </div>\n\n        {/* Context Menu */}\n        <ContextMenu />\n\n        {/* Cell Value Viewer */}\n        {viewedCell && (\n          <CellValueViewer\n            value={viewedCell.value}\n            columnName={viewedCell.columnName}\n            rowIndex={viewedCell.rowIndex}\n            onClose={() => setViewedCell(null)}\n          />\n        )}\n\n        {/* Column Stats Panel */}\n        {showStatsPanel && (\n          <ColumnStatsPanel\n            data={table.getFilteredRowModel().rows.map((r) => r.original)}\n            onClose={() => setShowStatsPanel(false)}\n            isMinimized={statsPanelMinimized}\n            onToggleMinimize={() => setStatsPanelMinimized(!statsPanelMinimized)}\n          />\n        )}\n\n        {/* Mobile stats */}\n        <div className=\"md:hidden flex items-center justify-between text-xs text-muted-foreground py-2 border-t border-border/30 mt-2 flex-shrink-0\">\n          <span>\n            {table.getFilteredRowModel().rows.length} of {data.length} rows\n          </span>\n          <div className=\"flex items-center gap-x-3\">\n            {executionTime !== null && executionTime !== undefined && (\n              <div className=\"flex items-center gap-1\">\n                <Clock className=\"h-3 w-3\" />\n                <span>{formatDuration(executionTime)}</span>\n              </div>\n            )}\n            {responseSize !== null && responseSize !== undefined && (\n              <div className=\"flex items-center\">\n                <span>{formatBytes(responseSize)}</span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </TooltipProvider>\n  );\n};\n\nexport default React.memo(DuckUITable);\n"
  },
  {
    "path": "src/components/theme/mode-toggle.tsx",
    "content": "import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"outline\" size=\"icon\">\n          <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n          <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n          <span className=\"sr-only\">Toggle theme</span>\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>Light</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>Dark</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>System</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "src/components/theme/theme-provider.tsx",
    "content": "import { createContext, useContext, useEffect, useState } from \"react\";\n\ntype Theme = \"dark\" | \"light\" | \"system\";\n\ntype ThemeProviderProps = {\n  children: React.ReactNode;\n  defaultTheme?: Theme;\n  storageKey?: string;\n};\n\ntype ThemeProviderState = {\n  theme: Theme;\n  setTheme: (theme: Theme) => void;\n};\n\nconst initialState: ThemeProviderState = {\n  theme: \"system\",\n  setTheme: () => null,\n};\n\nconst ThemeProviderContext = createContext<ThemeProviderState>(initialState);\n\nexport function ThemeProvider({\n  children,\n  defaultTheme = \"system\",\n  storageKey = \"vite-ui-theme\",\n  ...props\n}: ThemeProviderProps) {\n  const [theme, setTheme] = useState<Theme>(\n    () => (localStorage.getItem(storageKey) as Theme) || defaultTheme\n  );\n\n  useEffect(() => {\n    const root = window.document.documentElement;\n\n    root.classList.remove(\"light\", \"dark\");\n\n    if (theme === \"system\") {\n      const systemTheme = window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n        ? \"dark\"\n        : \"light\";\n\n      root.classList.add(systemTheme);\n      return;\n    }\n\n    root.classList.add(theme);\n  }, [theme]);\n\n  const value = {\n    theme,\n    setTheme: (theme: Theme) => {\n      localStorage.setItem(storageKey, theme);\n      setTheme(theme);\n    },\n  };\n\n  return (\n    <ThemeProviderContext.Provider {...props} value={value}>\n      {children}\n    </ThemeProviderContext.Provider>\n  );\n}\n\nexport const useTheme = () => {\n  const context = useContext(ThemeProviderContext);\n\n  if (context === undefined) throw new Error(\"useTheme must be used within a ThemeProvider\");\n\n  return context;\n};\n"
  },
  {
    "path": "src/components/ui/accordion.tsx",
    "content": "import * as React from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Accordion = AccordionPrimitive.Root;\n\nconst AccordionItem = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n  <AccordionPrimitive.Item ref={ref} className={cn(\"border-b\", className)} {...props} />\n));\nAccordionItem.displayName = \"AccordionItem\";\n\nconst AccordionTrigger = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Header className=\"flex\">\n    <AccordionPrimitive.Trigger\n      ref={ref}\n      className={cn(\n        \"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronDown className=\"h-4 w-4 shrink-0 transition-transform duration-200\" />\n    </AccordionPrimitive.Trigger>\n  </AccordionPrimitive.Header>\n));\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;\n\nconst AccordionContent = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Content\n    ref={ref}\n    className=\"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\"\n    {...props}\n  >\n    <div className={cn(\"pb-4 pt-0\", className)}>{children}</div>\n  </AccordionPrimitive.Content>\n));\n\nAccordionContent.displayName = AccordionPrimitive.Content.displayName;\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
  },
  {
    "path": "src/components/ui/alert-dialog.tsx",
    "content": "import * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/components/ui/button\";\n\nconst AlertDialog = AlertDialogPrimitive.Root;\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\n\nconst AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"flex flex-col space-y-2 text-center sm:text-left\", className)} {...props} />\n);\nAlertDialogHeader.displayName = \"AlertDialogHeader\";\n\nconst AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", className)}\n    {...props}\n  />\n);\nAlertDialogFooter.displayName = \"AlertDialogFooter\";\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold\", className)}\n    {...props}\n  />\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nAlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(buttonVariants({ variant: \"outline\" }), \"mt-2 sm:mt-0\", className)}\n    {...props}\n  />\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n  AlertDialog,\n  AlertDialogPortal,\n  AlertDialogOverlay,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel,\n};\n"
  },
  {
    "path": "src/components/ui/alert.tsx",
    "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst alertVariants = cva(\n  \"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-background text-foreground\",\n        destructive:\n          \"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n);\n\nconst Alert = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>\n>(({ className, variant, ...props }, ref) => (\n  <div ref={ref} role=\"alert\" className={cn(alertVariants({ variant }), className)} {...props} />\n));\nAlert.displayName = \"Alert\";\n\nconst AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(\n  ({ className, ...props }, ref) => (\n    <h5\n      ref={ref}\n      className={cn(\"mb-1 font-medium leading-none tracking-tight\", className)}\n      {...props}\n    />\n  )\n);\nAlertTitle.displayName = \"AlertTitle\";\n\nconst AlertDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"text-sm [&_p]:leading-relaxed\", className)} {...props} />\n));\nAlertDescription.displayName = \"AlertDescription\";\n\nexport { Alert, AlertTitle, AlertDescription };\n"
  },
  {
    "path": "src/components/ui/aspect-ratio.tsx",
    "content": "\"use client\";\n\nimport * as AspectRatioPrimitive from \"@radix-ui/react-aspect-ratio\";\n\nconst AspectRatio = AspectRatioPrimitive.Root;\n\nexport { AspectRatio };\n"
  },
  {
    "path": "src/components/ui/badge.tsx",
    "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst badgeVariants = cva(\n  \"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n  {\n    variants: {\n      variant: {\n        default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n        secondary:\n          \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        destructive:\n          \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n        outline: \"text-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n);\n\nexport interface BadgeProps\n  extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n  return <div className={cn(badgeVariants({ variant }), className)} {...props} />;\n}\n\nexport { Badge, badgeVariants };\n"
  },
  {
    "path": "src/components/ui/breadcrumb.tsx",
    "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Breadcrumb = React.forwardRef<\n  HTMLElement,\n  React.ComponentPropsWithoutRef<\"nav\"> & {\n    separator?: React.ReactNode;\n  }\n>(({ ...props }, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />);\nBreadcrumb.displayName = \"Breadcrumb\";\n\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<\"ol\">>(\n  ({ className, ...props }, ref) => (\n    <ol\n      ref={ref}\n      className={cn(\n        \"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5\",\n        className\n      )}\n      {...props}\n    />\n  )\n);\nBreadcrumbList.displayName = \"BreadcrumbList\";\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<\"li\">>(\n  ({ className, ...props }, ref) => (\n    <li ref={ref} className={cn(\"inline-flex items-center gap-1.5\", className)} {...props} />\n  )\n);\nBreadcrumbItem.displayName = \"BreadcrumbItem\";\n\nconst BreadcrumbLink = React.forwardRef<\n  HTMLAnchorElement,\n  React.ComponentPropsWithoutRef<\"a\"> & {\n    asChild?: boolean;\n  }\n>(({ asChild, className, ...props }, ref) => {\n  const Comp = asChild ? Slot : \"a\";\n\n  return (\n    <Comp\n      ref={ref}\n      className={cn(\"transition-colors hover:text-foreground\", className)}\n      {...props}\n    />\n  );\n});\nBreadcrumbLink.displayName = \"BreadcrumbLink\";\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<\"span\">>(\n  ({ className, ...props }, ref) => (\n    <span\n      ref={ref}\n      role=\"link\"\n      aria-disabled=\"true\"\n      aria-current=\"page\"\n      className={cn(\"font-normal text-foreground\", className)}\n      {...props}\n    />\n  )\n);\nBreadcrumbPage.displayName = \"BreadcrumbPage\";\n\nconst BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<\"li\">) => (\n  <li\n    role=\"presentation\"\n    aria-hidden=\"true\"\n    className={cn(\"[&>svg]:w-3.5 [&>svg]:h-3.5\", className)}\n    {...props}\n  >\n    {children ?? <ChevronRight />}\n  </li>\n);\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\";\n\nconst BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<\"span\">) => (\n  <span\n    role=\"presentation\"\n    aria-hidden=\"true\"\n    className={cn(\"flex h-9 w-9 items-center justify-center\", className)}\n    {...props}\n  >\n    <MoreHorizontal className=\"h-4 w-4\" />\n    <span className=\"sr-only\">More</span>\n  </span>\n);\nBreadcrumbEllipsis.displayName = \"BreadcrumbElipssis\";\n\nexport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  BreadcrumbEllipsis,\n};\n"
  },
  {
    "path": "src/components/ui/button.tsx",
    "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive: \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n        outline: \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n        secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-10 px-4 py-2\",\n        sm: \"h-9 rounded-md px-3\",\n        lg: \"h-11 rounded-md px-8\",\n        icon: \"h-10 w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\";\n    return (\n      <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />\n    );\n  }\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
  },
  {
    "path": "src/components/ui/card.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(\"rounded-lg border bg-card text-card-foreground shadow-sm\", className)}\n      {...props}\n    />\n  )\n);\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn(\"flex flex-col space-y-1.5 p-6\", className)} {...props} />\n  )\n);\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(\"text-2xl font-semibold leading-none tracking-tight\", className)}\n      {...props}\n    />\n  )\n);\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn(\"text-sm text-muted-foreground\", className)} {...props} />\n  )\n);\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n  )\n);\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div ref={ref} className={cn(\"flex items-center p-6 pt-0\", className)} {...props} />\n  )\n);\nCardFooter.displayName = \"CardFooter\";\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };\n"
  },
  {
    "path": "src/components/ui/checkbox.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { Check } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Checkbox = React.forwardRef<\n  React.ElementRef<typeof CheckboxPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <CheckboxPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n      className\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator className={cn(\"flex items-center justify-center text-current\")}>\n      <Check className=\"h-4 w-4\" />\n    </CheckboxPrimitive.Indicator>\n  </CheckboxPrimitive.Root>\n));\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\n\nexport { Checkbox };\n"
  },
  {
    "path": "src/components/ui/collapsible.tsx",
    "content": "import * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\";\n\nconst Collapsible = CollapsiblePrimitive.Root;\n\nconst CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;\n\nconst CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
  },
  {
    "path": "src/components/ui/command.tsx",
    "content": "import * as React from \"react\";\nimport { type DialogProps } from \"@radix-ui/react-dialog\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { Search } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Dialog, DialogContent } from \"@/components/ui/dialog\";\n\nconst Command = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive\n    ref={ref}\n    className={cn(\n      \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n      className\n    )}\n    {...props}\n  />\n));\nCommand.displayName = CommandPrimitive.displayName;\n\nconst CommandDialog = ({ children, ...props }: DialogProps) => {\n  return (\n    <Dialog {...props}>\n      <DialogContent className=\"overflow-hidden p-0 shadow-lg\" aria-describedby={undefined}>\n        <Command className=\"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n          {children}\n        </Command>\n      </DialogContent>\n    </Dialog>\n  );\n};\n\nconst CommandInput = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Input>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>\n>(({ className, ...props }, ref) => (\n  <div className=\"flex items-center border-b px-3\" cmdk-input-wrapper=\"\">\n    <Search className=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n    <CommandPrimitive.Input\n      ref={ref}\n      className={cn(\n        \"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  </div>\n));\n\nCommandInput.displayName = CommandPrimitive.Input.displayName;\n\nconst CommandList = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.List\n    ref={ref}\n    className={cn(\"max-h-[300px] overflow-y-auto overflow-x-hidden\", className)}\n    {...props}\n  />\n));\n\nCommandList.displayName = CommandPrimitive.List.displayName;\n\nconst CommandEmpty = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Empty>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>\n>((props, ref) => (\n  <CommandPrimitive.Empty ref={ref} className=\"py-6 text-center text-sm\" {...props} />\n));\n\nCommandEmpty.displayName = CommandPrimitive.Empty.displayName;\n\nconst CommandGroup = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Group>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.Group\n    ref={ref}\n    className={cn(\n      \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n      className\n    )}\n    {...props}\n  />\n));\n\nCommandGroup.displayName = CommandPrimitive.Group.displayName;\n\nconst CommandSeparator = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 h-px bg-border\", className)}\n    {...props}\n  />\n));\nCommandSeparator.displayName = CommandPrimitive.Separator.displayName;\n\nconst CommandItem = React.forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>\n>(({ className, ...props }, ref) => (\n  <CommandPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n      className\n    )}\n    {...props}\n  />\n));\n\nCommandItem.displayName = CommandPrimitive.Item.displayName;\n\nconst CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n};\nCommandShortcut.displayName = \"CommandShortcut\";\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
  },
  {
    "path": "src/components/ui/context-menu.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst ContextMenu = ContextMenuPrimitive.Root;\n\nconst ContextMenuTrigger = ContextMenuPrimitive.Trigger;\n\nconst ContextMenuGroup = ContextMenuPrimitive.Group;\n\nconst ContextMenuPortal = ContextMenuPrimitive.Portal;\n\nconst ContextMenuSub = ContextMenuPrimitive.Sub;\n\nconst ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;\n\nconst ContextMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {\n    inset?: boolean;\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <ContextMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\n  </ContextMenuPrimitive.SubTrigger>\n));\nContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;\n\nconst ContextMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n));\nContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;\n\nconst ContextMenuContent = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.Portal>\n    <ContextMenuPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </ContextMenuPrimitive.Portal>\n));\nContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;\n\nconst ContextMenuItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <ContextMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n));\nContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;\n\nconst ContextMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <ContextMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <ContextMenuPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </ContextMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </ContextMenuPrimitive.CheckboxItem>\n));\nContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;\n\nconst ContextMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <ContextMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <ContextMenuPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </ContextMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </ContextMenuPrimitive.RadioItem>\n));\nContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;\n\nconst ContextMenuLabel = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <ContextMenuPrimitive.Label\n    ref={ref}\n    className={cn(\"px-2 py-1.5 text-sm font-semibold text-foreground\", inset && \"pl-8\", className)}\n    {...props}\n  />\n));\nContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;\n\nconst ContextMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n    {...props}\n  />\n));\nContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;\n\nconst ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n};\nContextMenuShortcut.displayName = \"ContextMenuShortcut\";\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup,\n};\n"
  },
  {
    "path": "src/components/ui/dialog.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Dialog = DialogPrimitive.Root;\n\nconst DialogTrigger = DialogPrimitive.Trigger;\n\nconst DialogPortal = DialogPrimitive.Portal;\n\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    ref={ref}\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n  />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <DialogPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close>\n    </DialogPrimitive.Content>\n  </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"flex flex-col space-y-1.5 text-center sm:text-left\", className)} {...props} />\n);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", className)}\n    {...props}\n  />\n);\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold leading-none tracking-tight\", className)}\n    {...props}\n  />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n  Dialog,\n  DialogPortal,\n  DialogOverlay,\n  DialogClose,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n};\n"
  },
  {
    "path": "src/components/ui/drawer.tsx",
    "content": "import * as React from \"react\";\nimport { Drawer as DrawerPrimitive } from \"vaul\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Drawer = ({\n  shouldScaleBackground = true,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (\n  <DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />\n);\nDrawer.displayName = \"Drawer\";\n\nconst DrawerTrigger = DrawerPrimitive.Trigger;\n\nconst DrawerPortal = DrawerPrimitive.Portal;\n\nconst DrawerClose = DrawerPrimitive.Close;\n\nconst DrawerOverlay = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <DrawerPrimitive.Overlay\n    ref={ref}\n    className={cn(\"fixed inset-0 z-50 bg-black/80\", className)}\n    {...props}\n  />\n));\nDrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;\n\nconst DrawerContent = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DrawerPortal>\n    <DrawerOverlay />\n    <DrawerPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted\" />\n      {children}\n    </DrawerPrimitive.Content>\n  </DrawerPortal>\n));\nDrawerContent.displayName = \"DrawerContent\";\n\nconst DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"grid gap-1.5 p-4 text-center sm:text-left\", className)} {...props} />\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\nconst DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\nconst DrawerTitle = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DrawerPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold leading-none tracking-tight\", className)}\n    {...props}\n  />\n));\nDrawerTitle.displayName = DrawerPrimitive.Title.displayName;\n\nconst DrawerDescription = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DrawerPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nDrawerDescription.displayName = DrawerPrimitive.Description.displayName;\n\nexport {\n  Drawer,\n  DrawerPortal,\n  DrawerOverlay,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerFooter,\n  DrawerTitle,\n  DrawerDescription,\n};\n"
  },
  {
    "path": "src/components/ui/dropdown-menu.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean;\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto\" />\n  </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n));\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <DropdownMenuPrimitive.Portal>\n    <DropdownMenuPrimitive.Content\n      ref={ref}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <DropdownMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Label\n    ref={ref}\n    className={cn(\"px-2 py-1.5 text-sm font-semibold\", inset && \"pl-8\", className)}\n    {...props}\n  />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)} {...props} />\n  );\n};\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuGroup,\n  DropdownMenuPortal,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuRadioGroup,\n};\n"
  },
  {
    "path": "src/components/ui/form.tsx",
    "content": "import * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n  Controller,\n  ControllerProps,\n  FieldPath,\n  FieldValues,\n  FormProvider,\n  useFormContext,\n} from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Label } from \"@/components/ui/label\";\n\nconst Form = FormProvider;\n\ntype FormFieldContextValue<\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = {\n  name: TName;\n};\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);\n\nconst FormField = <\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n  ...props\n}: ControllerProps<TFieldValues, TName>) => {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  );\n};\n\nconst useFormField = () => {\n  const fieldContext = React.useContext(FormFieldContext);\n  const itemContext = React.useContext(FormItemContext);\n  const { getFieldState, formState } = useFormContext();\n\n  const fieldState = getFieldState(fieldContext.name, formState);\n\n  if (!fieldContext) {\n    throw new Error(\"useFormField should be used within <FormField>\");\n  }\n\n  const { id } = itemContext;\n\n  return {\n    id,\n    name: fieldContext.name,\n    formItemId: `${id}-form-item`,\n    formDescriptionId: `${id}-form-item-description`,\n    formMessageId: `${id}-form-item-message`,\n    ...fieldState,\n  };\n};\n\ntype FormItemContextValue = {\n  id: string;\n};\n\nconst FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);\n\nconst FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => {\n    const id = React.useId();\n\n    return (\n      <FormItemContext.Provider value={{ id }}>\n        <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n      </FormItemContext.Provider>\n    );\n  }\n);\nFormItem.displayName = \"FormItem\";\n\nconst FormLabel = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  const { error, formItemId } = useFormField();\n\n  return (\n    <Label\n      ref={ref}\n      className={cn(error && \"text-destructive\", className)}\n      htmlFor={formItemId}\n      {...props}\n    />\n  );\n});\nFormLabel.displayName = \"FormLabel\";\n\nconst FormControl = React.forwardRef<\n  React.ElementRef<typeof Slot>,\n  React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n  const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\n\n  return (\n    <Slot\n      ref={ref}\n      id={formItemId}\n      aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}\n      aria-invalid={!!error}\n      {...props}\n    />\n  );\n});\nFormControl.displayName = \"FormControl\";\n\nconst FormDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n  const { formDescriptionId } = useFormField();\n\n  return (\n    <p\n      ref={ref}\n      id={formDescriptionId}\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n});\nFormDescription.displayName = \"FormDescription\";\n\nconst FormMessage = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n  const { error, formMessageId } = useFormField();\n  const body = error ? String(error?.message) : children;\n\n  if (!body) {\n    return null;\n  }\n\n  return (\n    <p\n      ref={ref}\n      id={formMessageId}\n      className={cn(\"text-sm font-medium text-destructive\", className)}\n      {...props}\n    >\n      {body}\n    </p>\n  );\n});\nFormMessage.displayName = \"FormMessage\";\n\nexport {\n  useFormField,\n  Form,\n  FormItem,\n  FormLabel,\n  FormControl,\n  FormDescription,\n  FormMessage,\n  FormField,\n};\n"
  },
  {
    "path": "src/components/ui/hover-card.tsx",
    "content": "import * as React from \"react\";\nimport * as HoverCardPrimitive from \"@radix-ui/react-hover-card\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst HoverCard = HoverCardPrimitive.Root;\n\nconst HoverCardTrigger = HoverCardPrimitive.Trigger;\n\nconst HoverCardContent = React.forwardRef<\n  React.ElementRef<typeof HoverCardPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n  <HoverCardPrimitive.Content\n    ref={ref}\n    align={align}\n    sideOffset={sideOffset}\n    className={cn(\n      \"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n));\nHoverCardContent.displayName = HoverCardPrimitive.Content.displayName;\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n"
  },
  {
    "path": "src/components/ui/input.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    );\n  }\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
  },
  {
    "path": "src/components/ui/label.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst labelVariants = cva(\n  \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n);\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n"
  },
  {
    "path": "src/components/ui/menubar.tsx",
    "content": "import * as React from \"react\";\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst MenubarMenu = MenubarPrimitive.Menu;\n\nconst MenubarGroup = MenubarPrimitive.Group;\n\nconst MenubarPortal = MenubarPrimitive.Portal;\n\nconst MenubarSub = MenubarPrimitive.Sub;\n\nconst MenubarRadioGroup = MenubarPrimitive.RadioGroup;\n\nconst Menubar = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"flex h-10 items-center space-x-1 rounded-md border bg-background p-1\",\n      className\n    )}\n    {...props}\n  />\n));\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n      className\n    )}\n    {...props}\n  />\n));\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {\n    inset?: boolean;\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <MenubarPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\n  </MenubarPrimitive.SubTrigger>\n));\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n));\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>\n>(({ className, align = \"start\", alignOffset = -4, sideOffset = 8, ...props }, ref) => (\n  <MenubarPrimitive.Portal>\n    <MenubarPrimitive.Content\n      ref={ref}\n      align={align}\n      alignOffset={alignOffset}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </MenubarPrimitive.Portal>\n));\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <MenubarPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n));\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <MenubarPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <MenubarPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </MenubarPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </MenubarPrimitive.CheckboxItem>\n));\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <MenubarPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <MenubarPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </MenubarPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </MenubarPrimitive.RadioItem>\n));\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <MenubarPrimitive.Label\n    ref={ref}\n    className={cn(\"px-2 py-1.5 text-sm font-semibold\", inset && \"pl-8\", className)}\n    {...props}\n  />\n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nconst MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n};\nMenubarShortcut.displayname = \"MenubarShortcut\";\n\nexport {\n  Menubar,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarItem,\n  MenubarSeparator,\n  MenubarLabel,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarPortal,\n  MenubarSubContent,\n  MenubarSubTrigger,\n  MenubarGroup,\n  MenubarSub,\n  MenubarShortcut,\n};\n"
  },
  {
    "path": "src/components/ui/multi-select.tsx",
    "content": "import * as React from \"react\";\nimport { X, Check, ChevronsUpDown } from \"lucide-react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface MultiSelectOption {\n  label: string;\n  value: string;\n  color?: string;\n}\n\ninterface MultiSelectProps {\n  options: MultiSelectOption[];\n  selected: string[];\n  onChange: (selected: string[]) => void;\n  placeholder?: string;\n  maxSelected?: number;\n  className?: string;\n}\n\nexport function MultiSelect({\n  options,\n  selected,\n  onChange,\n  placeholder = \"Select items...\",\n  maxSelected,\n  className,\n}: MultiSelectProps) {\n  const [open, setOpen] = React.useState(false);\n\n  const handleSelect = (value: string) => {\n    if (selected.includes(value)) {\n      onChange(selected.filter((item) => item !== value));\n    } else {\n      if (maxSelected && selected.length >= maxSelected) {\n        return;\n      }\n      onChange([...selected, value]);\n    }\n  };\n\n  const handleRemove = (value: string, e: React.MouseEvent) => {\n    e.stopPropagation();\n    onChange(selected.filter((item) => item !== value));\n  };\n\n  const selectedOptions = options.filter((option) => selected.includes(option.value));\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          role=\"combobox\"\n          aria-expanded={open}\n          className={cn(\"w-full justify-between min-h-10 h-auto\", className)}\n        >\n          <div className=\"flex gap-1 overflow-x-auto flex-1 mr-2  scrollbar-thin\">\n            {selectedOptions.length > 0 ? (\n              selectedOptions.map((option) => (\n                <Badge\n                  key={option.value}\n                  className=\"shrink-0 gap-1.5 font-medium\"\n                  style={{\n                    backgroundColor: option.color ? `${option.color}65` : undefined,\n                    borderColor: option.color || undefined,\n                    borderWidth: \"2px\",\n                    boxShadow: option.color ? `0 1px 3px ${option.color}30` : undefined,\n                  }}\n                >\n                  {option.color && (\n                    <div\n                      className=\"w-2.5 h-2.5 rounded-full shrink-0\"\n                      style={{ backgroundColor: option.color }}\n                    />\n                  )}\n                  <span className=\"whitespace-nowrap text-foreground\">{option.label}</span>\n                  <X\n                    className=\"h-3.5 w-3.5 cursor-pointer opacity-70 hover:opacity-100 shrink-0\"\n                    onClick={(e) => handleRemove(option.value, e)}\n                  />\n                </Badge>\n              ))\n            ) : (\n              <span className=\"text-muted-foreground text-sm\">{placeholder}</span>\n            )}\n          </div>\n          <ChevronsUpDown className=\"h-4 w-4 shrink-0 opacity-50\" />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-[300px] p-0\" align=\"start\">\n        <Command>\n          <CommandInput placeholder=\"Search...\" className=\"h-9\" />\n          <CommandList>\n            <CommandEmpty>No items found.</CommandEmpty>\n            <CommandGroup>\n              {options.map((option) => {\n                const isSelected = selected.includes(option.value);\n                const isDisabled = Boolean(\n                  !isSelected && maxSelected && selected.length >= maxSelected\n                );\n\n                return (\n                  <CommandItem\n                    key={option.value}\n                    value={option.value}\n                    onSelect={() => handleSelect(option.value)}\n                    disabled={isDisabled}\n                    className=\"cursor-pointer\"\n                  >\n                    <div className=\"flex items-center gap-2.5 flex-1\">\n                      {option.color && (\n                        <div\n                          className=\"w-3 h-3 rounded shrink-0\"\n                          style={{\n                            backgroundColor: option.color,\n                            opacity: isSelected ? 1 : 0.5,\n                            border: `1px solid ${option.color}`,\n                          }}\n                        />\n                      )}\n                      <span className={isDisabled ? \"opacity-50\" : \"\"}>{option.label}</span>\n                    </div>\n                    {isSelected && <Check className=\"ml-auto h-4 w-4 opacity-100\" />}\n                  </CommandItem>\n                );\n              })}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n"
  },
  {
    "path": "src/components/ui/navigation-menu.tsx",
    "content": "import * as React from \"react\";\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\";\nimport { cva } from \"class-variance-authority\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst NavigationMenu = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Root\n    ref={ref}\n    className={cn(\"relative z-10 flex max-w-max flex-1 items-center justify-center\", className)}\n    {...props}\n  >\n    {children}\n    <NavigationMenuViewport />\n  </NavigationMenuPrimitive.Root>\n));\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;\n\nconst NavigationMenuList = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.List\n    ref={ref}\n    className={cn(\"group flex flex-1 list-none items-center justify-center space-x-1\", className)}\n    {...props}\n  />\n));\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item;\n\nconst navigationMenuTriggerStyle = cva(\n  \"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50\"\n);\n\nconst NavigationMenuTrigger = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Trigger\n    ref={ref}\n    className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n    {...props}\n  >\n    {children}{\" \"}\n    <ChevronDown\n      className=\"relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180\"\n      aria-hidden=\"true\"\n    />\n  </NavigationMenuPrimitive.Trigger>\n));\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;\n\nconst NavigationMenuContent = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto \",\n      className\n    )}\n    {...props}\n  />\n));\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link;\n\nconst NavigationMenuViewport = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n  <div className={cn(\"absolute left-0 top-full flex justify-center\")}>\n    <NavigationMenuPrimitive.Viewport\n      className={cn(\n        \"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]\",\n        className\n      )}\n      ref={ref}\n      {...props}\n    />\n  </div>\n));\nNavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;\n\nconst NavigationMenuIndicator = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Indicator\n    ref={ref}\n    className={cn(\n      \"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md\" />\n  </NavigationMenuPrimitive.Indicator>\n));\nNavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;\n\nexport {\n  navigationMenuTriggerStyle,\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n};\n"
  },
  {
    "path": "src/components/ui/pagination.tsx",
    "content": "import * as React from \"react\";\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ButtonProps, buttonVariants } from \"@/components/ui/button\";\n\nconst Pagination = ({ className, ...props }: React.ComponentProps<\"nav\">) => (\n  <nav\n    role=\"navigation\"\n    aria-label=\"pagination\"\n    className={cn(\"mx-auto flex w-full justify-center\", className)}\n    {...props}\n  />\n);\nPagination.displayName = \"Pagination\";\n\nconst PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n  ({ className, ...props }, ref) => (\n    <ul ref={ref} className={cn(\"flex flex-row items-center gap-1\", className)} {...props} />\n  )\n);\nPaginationContent.displayName = \"PaginationContent\";\n\nconst PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n  ({ className, ...props }, ref) => <li ref={ref} className={cn(\"\", className)} {...props} />\n);\nPaginationItem.displayName = \"PaginationItem\";\n\ntype PaginationLinkProps = {\n  isActive?: boolean;\n} & Pick<ButtonProps, \"size\"> &\n  React.ComponentProps<\"a\">;\n\nconst PaginationLink = ({ className, isActive, size = \"icon\", ...props }: PaginationLinkProps) => (\n  <a\n    aria-current={isActive ? \"page\" : undefined}\n    className={cn(\n      buttonVariants({\n        variant: isActive ? \"outline\" : \"ghost\",\n        size,\n      }),\n      className\n    )}\n    {...props}\n  />\n);\nPaginationLink.displayName = \"PaginationLink\";\n\nconst PaginationPrevious = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) => (\n  <PaginationLink\n    aria-label=\"Go to previous page\"\n    size=\"default\"\n    className={cn(\"gap-1 pl-2.5\", className)}\n    {...props}\n  >\n    <ChevronLeft className=\"h-4 w-4\" />\n    <span>Previous</span>\n  </PaginationLink>\n);\nPaginationPrevious.displayName = \"PaginationPrevious\";\n\nconst PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (\n  <PaginationLink\n    aria-label=\"Go to next page\"\n    size=\"default\"\n    className={cn(\"gap-1 pr-2.5\", className)}\n    {...props}\n  >\n    <span>Next</span>\n    <ChevronRight className=\"h-4 w-4\" />\n  </PaginationLink>\n);\nPaginationNext.displayName = \"PaginationNext\";\n\nconst PaginationEllipsis = ({ className, ...props }: React.ComponentProps<\"span\">) => (\n  <span\n    aria-hidden\n    className={cn(\"flex h-9 w-9 items-center justify-center\", className)}\n    {...props}\n  >\n    <MoreHorizontal className=\"h-4 w-4\" />\n    <span className=\"sr-only\">More pages</span>\n  </span>\n);\nPaginationEllipsis.displayName = \"PaginationEllipsis\";\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious,\n};\n"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "content": "import * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Popover = PopoverPrimitive.Root;\n\nconst PopoverTrigger = PopoverPrimitive.Trigger;\n\nconst PopoverContent = React.forwardRef<\n  React.ElementRef<typeof PopoverPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Content\n      ref={ref}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </PopoverPrimitive.Portal>\n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverTrigger, PopoverContent };\n"
  },
  {
    "path": "src/components/ui/progress.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Progress = React.forwardRef<\n  React.ElementRef<typeof ProgressPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>\n>(({ className, value, ...props }, ref) => (\n  <ProgressPrimitive.Root\n    ref={ref}\n    className={cn(\"relative h-4 w-full overflow-hidden rounded-full bg-secondary\", className)}\n    {...props}\n  >\n    <ProgressPrimitive.Indicator\n      className=\"h-full w-full flex-1 bg-primary transition-all\"\n      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n    />\n  </ProgressPrimitive.Root>\n));\nProgress.displayName = ProgressPrimitive.Root.displayName;\n\nexport { Progress };\n"
  },
  {
    "path": "src/components/ui/radio-group.tsx",
    "content": "import * as React from \"react\";\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { Circle } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst RadioGroup = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  return <RadioGroupPrimitive.Root className={cn(\"grid gap-2\", className)} {...props} ref={ref} />;\n});\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\n\nconst RadioGroupItem = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        \"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator className=\"flex items-center justify-center\">\n        <Circle className=\"h-2.5 w-2.5 fill-current text-current\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  );\n});\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\n\nexport { RadioGroup, RadioGroupItem };\n"
  },
  {
    "path": "src/components/ui/resizable.tsx",
    "content": "\"use client\";\n\nimport { GripVertical } from \"lucide-react\";\nimport * as ResizablePrimitive from \"react-resizable-panels\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst ResizablePanelGroup = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (\n  <ResizablePrimitive.PanelGroup\n    className={cn(\"flex h-full w-full data-[panel-group-direction=vertical]:flex-col\", className)}\n    {...props}\n  />\n);\n\nconst ResizablePanel = ResizablePrimitive.Panel;\n\nconst ResizableHandle = ({\n  withHandle,\n  className,\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n  withHandle?: boolean;\n}) => (\n  <ResizablePrimitive.PanelResizeHandle\n    className={cn(\n      \"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n      className\n    )}\n    {...props}\n  >\n    {withHandle && (\n      <div className=\"z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border\">\n        <GripVertical className=\"h-2.5 w-2.5\" />\n      </div>\n    )}\n  </ResizablePrimitive.PanelResizeHandle>\n);\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle };\n"
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "content": "import * as React from \"react\";\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst ScrollArea = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <ScrollAreaPrimitive.Root\n    ref={ref}\n    className={cn(\"relative overflow-hidden\", className)}\n    {...props}\n  >\n    <ScrollAreaPrimitive.Viewport className=\"h-full w-full rounded-[inherit]\">\n      {children}\n    </ScrollAreaPrimitive.Viewport>\n    <ScrollBar />\n    <ScrollAreaPrimitive.Corner />\n  </ScrollAreaPrimitive.Root>\n));\nScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;\n\nconst ScrollBar = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n  <ScrollAreaPrimitive.ScrollAreaScrollbar\n    ref={ref}\n    orientation={orientation}\n    className={cn(\n      \"flex touch-none select-none transition-colors\",\n      orientation === \"vertical\" && \"h-full w-2.5 border-l border-l-transparent p-[1px]\",\n      orientation === \"horizontal\" && \"h-2.5 flex-col border-t border-t-transparent p-[1px]\",\n      className\n    )}\n    {...props}\n  >\n    <ScrollAreaPrimitive.ScrollAreaThumb className=\"relative flex-1 rounded-full bg-border\" />\n  </ScrollAreaPrimitive.ScrollAreaScrollbar>\n));\nScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;\n\nexport { ScrollArea, ScrollBar };\n"
  },
  {
    "path": "src/components/ui/select.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nconst SelectGroup = SelectPrimitive.Group;\n\nconst SelectValue = SelectPrimitive.Value;\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDown className=\"h-4 w-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n    {...props}\n  >\n    <ChevronUp className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n    {...props}\n  >\n    <ChevronDown className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        position === \"popper\" &&\n          \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n        className\n      )}\n      position={position}\n      {...props}\n    >\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          \"p-1\",\n          position === \"popper\" &&\n            \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n        )}\n      >\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\"py-1.5 pl-8 pr-2 text-sm font-semibold\", className)}\n    {...props}\n  />\n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton,\n};\n"
  },
  {
    "path": "src/components/ui/separator.tsx",
    "content": "import * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Separator = React.forwardRef<\n  React.ElementRef<typeof SeparatorPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>\n>(({ className, orientation = \"horizontal\", decorative = true, ...props }, ref) => (\n  <SeparatorPrimitive.Root\n    ref={ref}\n    decorative={decorative}\n    orientation={orientation}\n    className={cn(\n      \"shrink-0 bg-border\",\n      orientation === \"horizontal\" ? \"h-[1px] w-full\" : \"h-full w-[1px]\",\n      className\n    )}\n    {...props}\n  />\n));\nSeparator.displayName = SeparatorPrimitive.Root.displayName;\n\nexport { Separator };\n"
  },
  {
    "path": "src/components/ui/sheet.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Sheet = SheetPrimitive.Root;\n\nconst SheetTrigger = SheetPrimitive.Trigger;\n\nconst SheetClose = SheetPrimitive.Close;\n\nconst SheetPortal = SheetPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n  \"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n  {\n    variants: {\n      side: {\n        top: \"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n        bottom:\n          \"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n        left: \"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n        right:\n          \"inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n      },\n    },\n    defaultVariants: {\n      side: \"right\",\n    },\n  }\n);\n\ninterface SheetContentProps\n  extends\n    React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n    VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Content>,\n  SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n  <SheetPortal>\n    <SheetOverlay />\n    <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>\n      {children}\n      <SheetPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </SheetPrimitive.Close>\n    </SheetPrimitive.Content>\n  </SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"flex flex-col space-y-2 text-center sm:text-left\", className)} {...props} />\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", className)}\n    {...props}\n  />\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold text-foreground\", className)}\n    {...props}\n  />\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n  Sheet,\n  SheetPortal,\n  SheetOverlay,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
  },
  {
    "path": "src/components/ui/skeleton.tsx",
    "content": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  return <div className={cn(\"animate-pulse rounded-md bg-muted\", className)} {...props} />;\n}\n\nexport { Skeleton };\n"
  },
  {
    "path": "src/components/ui/sonner.tsx",
    "content": "import { useTheme } from \"@/components/theme/theme-provider\";\nimport { Toaster as Sonner } from \"sonner\";\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme();\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      toastOptions={{\n        classNames: {\n          toast:\n            \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n          description: \"group-[.toast]:text-muted-foreground\",\n          actionButton: \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n          cancelButton: \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n        },\n      }}\n      {...props}\n    />\n  );\n};\n\nexport { Toaster };\n"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      \"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  >\n    <SwitchPrimitives.Thumb\n      className={cn(\n        \"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\"\n      )}\n    />\n  </SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n"
  },
  {
    "path": "src/components/ui/table.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(\n  ({ className, ...props }, ref) => (\n    <div className=\"relative w-full overflow-auto\">\n      <table ref={ref} className={cn(\"w-full caption-bottom text-sm\", className)} {...props} />\n    </div>\n  )\n);\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n));\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tfoot\n    ref={ref}\n    className={cn(\"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\", className)}\n    {...props}\n  />\n));\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(\n  ({ className, ...props }, ref) => (\n    <tr\n      ref={ref}\n      className={cn(\n        \"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n        className\n      )}\n      {...props}\n    />\n  )\n);\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<\n  HTMLTableCellElement,\n  React.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <th\n    ref={ref}\n    className={cn(\n      \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n      className\n    )}\n    {...props}\n  />\n));\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<\n  HTMLTableCellElement,\n  React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <td\n    ref={ref}\n    className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)}\n    {...props}\n  />\n));\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<\n  HTMLTableCaptionElement,\n  React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n  <caption ref={ref} className={cn(\"mt-4 text-sm text-muted-foreground\", className)} {...props} />\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n"
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "content": "import * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.List\n    ref={ref}\n    className={cn(\n      \"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n"
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<\"textarea\">>(\n  ({ className, ...props }, ref) => {\n    return (\n      <textarea\n        className={cn(\n          \"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    );\n  }\n);\nTextarea.displayName = \"Textarea\";\n\nexport { Textarea };\n"
  },
  {
    "path": "src/components/ui/tooltip.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst TooltipProvider = TooltipPrimitive.Provider;\n\nconst Tooltip = TooltipPrimitive.Root;\n\nconst TooltipTrigger = TooltipPrimitive.Trigger;\n\nconst TooltipContent = React.forwardRef<\n  React.ElementRef<typeof TooltipPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <TooltipPrimitive.Content\n    ref={ref}\n    sideOffset={sideOffset}\n    className={cn(\n      \"z-10 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n));\nTooltipContent.displayName = TooltipPrimitive.Content.displayName;\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
  },
  {
    "path": "src/components/workspace/BrainTab.tsx",
    "content": "import { useState, useEffect } from \"react\";\nimport { useDuckStore, type AIProviderType } from \"@/store\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport {\n  Download,\n  Check,\n  Loader2,\n  AlertCircle,\n  Cpu,\n  HardDrive,\n  Zap,\n  Trash2,\n  Key,\n  Cloud,\n  Eye,\n  EyeOff,\n  Server,\n  Link,\n} from \"lucide-react\";\nimport { toast } from \"sonner\";\nimport { AVAILABLE_MODELS, type ModelConfig } from \"@/lib/duckBrain\";\nimport { OPENAI_MODELS, ANTHROPIC_MODELS } from \"@/lib/duckBrain/providers/types\";\n\nconst BrainTab = () => {\n  const duckBrain = useDuckStore((s) => s.duckBrain);\n  const initializeDuckBrain = useDuckStore((s) => s.initializeDuckBrain);\n  const setAIProvider = useDuckStore((s) => s.setAIProvider);\n  const updateProviderConfig = useDuckStore((s) => s.updateProviderConfig);\n  const [isClearing, setIsClearing] = useState(false);\n  const [cacheSize, setCacheSize] = useState<string | null>(null);\n  const [showApiKey, setShowApiKey] = useState<Record<string, boolean>>({});\n  const [apiKeyInputs, setApiKeyInputs] = useState<Record<string, string>>({});\n  const [isTesting, setIsTesting] = useState(false);\n\n  // OpenAI-compatible provider inputs\n  const [compatibleBaseUrl, setCompatibleBaseUrl] = useState(\"\");\n  const [compatibleModelId, setCompatibleModelId] = useState(\"\");\n  const [compatibleApiKey, setCompatibleApiKey] = useState(\"\");\n\n  useEffect(() => {\n    checkCacheSize();\n    const inputs: Record<string, string> = {};\n    if (duckBrain.providerConfigs.openai?.apiKey) {\n      inputs.openai = duckBrain.providerConfigs.openai.apiKey;\n    }\n    if (duckBrain.providerConfigs.anthropic?.apiKey) {\n      inputs.anthropic = duckBrain.providerConfigs.anthropic.apiKey;\n    }\n    setApiKeyInputs(inputs);\n\n    // Initialize openai-compatible inputs\n    const compatibleConfig = duckBrain.providerConfigs[\"openai-compatible\"];\n    if (compatibleConfig) {\n      setCompatibleBaseUrl(compatibleConfig.baseUrl || \"\");\n      setCompatibleModelId(compatibleConfig.modelId || \"\");\n      setCompatibleApiKey(compatibleConfig.apiKey || \"\");\n    }\n  }, []);\n\n  const checkCacheSize = async () => {\n    try {\n      if (\"storage\" in navigator && \"estimate\" in navigator.storage) {\n        const estimate = await navigator.storage.estimate();\n        if (estimate.usage) {\n          const sizeInMB = (estimate.usage / (1024 * 1024)).toFixed(1);\n          setCacheSize(`${sizeInMB} MB`);\n        }\n      }\n    } catch {\n      // Ignore errors\n    }\n  };\n\n  const handleClearCache = async () => {\n    setIsClearing(true);\n    try {\n      const databases = await indexedDB.databases();\n      let cleared = 0;\n\n      for (const db of databases) {\n        if (\n          db.name &&\n          (db.name.includes(\"webllm\") ||\n            db.name.includes(\"mlc\") ||\n            db.name.includes(\"cache\") ||\n            db.name.includes(\"model\"))\n        ) {\n          indexedDB.deleteDatabase(db.name);\n          cleared++;\n        }\n      }\n\n      if (\"caches\" in window) {\n        const cacheNames = await caches.keys();\n        for (const name of cacheNames) {\n          if (name.includes(\"webllm\") || name.includes(\"mlc\") || name.includes(\"model\")) {\n            await caches.delete(name);\n            cleared++;\n          }\n        }\n      }\n\n      toast.success(\n        cleared > 0\n          ? `Cleared ${cleared} cached items. Reload page to see changes.`\n          : \"No cached model data found.\"\n      );\n      checkCacheSize();\n    } catch (err) {\n      console.error(\"Failed to clear cache:\", err);\n      toast.error(\"Failed to clear cache. Try clearing manually via browser settings.\");\n    } finally {\n      setIsClearing(false);\n    }\n  };\n\n  const handleProviderChange = (provider: AIProviderType) => {\n    setAIProvider(provider);\n  };\n\n  const handleApiKeyChange = (provider: \"openai\" | \"anthropic\", value: string) => {\n    setApiKeyInputs((prev) => ({ ...prev, [provider]: value }));\n  };\n\n  const handleSaveApiKey = async (provider: \"openai\" | \"anthropic\") => {\n    const apiKey = apiKeyInputs[provider];\n    if (!apiKey) {\n      toast.error(\"Please enter an API key\");\n      return;\n    }\n\n    const currentConfig = duckBrain.providerConfigs[provider];\n    const defaultModel = provider === \"openai\" ? \"gpt-4o-mini\" : \"claude-sonnet-4-20250514\";\n\n    updateProviderConfig(provider, {\n      apiKey,\n      modelId: currentConfig?.modelId || defaultModel,\n    });\n\n    setIsTesting(true);\n    try {\n      const { testProviderConnection } = await import(\"@/lib/duckBrain/providers\");\n      const result = await testProviderConnection(provider, {\n        apiKey,\n        modelId: currentConfig?.modelId || defaultModel,\n      });\n\n      if (result.success) {\n        toast.success(\n          `${provider === \"openai\" ? \"OpenAI\" : \"Anthropic\"} API key saved and verified`\n        );\n      } else {\n        toast.error(`Connection failed: ${result.error}`);\n      }\n    } catch {\n      toast.success(\"API key saved\");\n    } finally {\n      setIsTesting(false);\n    }\n  };\n\n  const handleModelChange = (provider: \"openai\" | \"anthropic\", modelId: string) => {\n    const currentConfig = duckBrain.providerConfigs[provider];\n    updateProviderConfig(provider, {\n      apiKey: currentConfig?.apiKey || \"\",\n      modelId,\n    });\n  };\n\n  const handleSaveCompatibleConfig = async () => {\n    if (!compatibleBaseUrl) {\n      toast.error(\"Please enter a Base URL\");\n      return;\n    }\n    if (!compatibleModelId) {\n      toast.error(\"Please enter a Model ID\");\n      return;\n    }\n\n    setIsTesting(true);\n    try {\n      const { testProviderConnection } = await import(\"@/lib/duckBrain/providers\");\n      const result = await testProviderConnection(\"openai-compatible\", {\n        baseUrl: compatibleBaseUrl,\n        modelId: compatibleModelId,\n        apiKey: compatibleApiKey || undefined,\n      });\n\n      if (result.success) {\n        // Only save config if connection test succeeds\n        updateProviderConfig(\"openai-compatible\", {\n          baseUrl: compatibleBaseUrl,\n          modelId: compatibleModelId,\n          apiKey: compatibleApiKey || undefined,\n        });\n        toast.success(\"Connected successfully!\");\n      } else {\n        toast.error(`Connection failed: ${result.error || \"Unknown error\"}`);\n      }\n    } catch (err) {\n      const errorMessage = err instanceof Error ? err.message : \"Connection failed\";\n      toast.error(errorMessage);\n    } finally {\n      setIsTesting(false);\n    }\n  };\n\n  const {\n    modelStatus,\n    currentModel,\n    downloadProgress,\n    downloadStatus,\n    isWebGPUSupported,\n    error,\n    aiProvider = \"webllm\",\n    providerConfigs = {},\n  } = duckBrain;\n\n  const isDownloading = modelStatus === \"downloading\" || modelStatus === \"loading\";\n\n  const handleLoadModel = async (modelId: string) => {\n    try {\n      await initializeDuckBrain(modelId);\n    } catch (err) {\n      console.error(\"Failed to load model:\", err);\n    }\n  };\n\n  const getModelStatus = (model: ModelConfig) => {\n    if (currentModel === model.id) {\n      if (modelStatus === \"ready\") return \"ready\";\n      if (isDownloading) return \"downloading\";\n    }\n    return \"available\";\n  };\n\n  return (\n    <div className=\"p-4 space-y-6 overflow-auto h-full\">\n      {/* WebGPU Status Alert */}\n      {isWebGPUSupported === false && (\n        <Alert variant=\"destructive\">\n          <AlertCircle className=\"h-4 w-4\" />\n          <AlertDescription>\n            <strong>WebGPU Not Supported</strong> - Duck Brain requires WebGPU for local AI\n            inference. Please use Chrome 113+ or Edge 113+.\n          </AlertDescription>\n        </Alert>\n      )}\n\n      {/* Provider Selection */}\n      <Card>\n        <CardHeader>\n          <div className=\"flex items-center gap-2\">\n            <Cloud className=\"h-5 w-5 text-primary\" />\n            <CardTitle>AI Provider</CardTitle>\n          </div>\n          <CardDescription>\n            Choose between local (WebLLM) or cloud-based AI providers.\n          </CardDescription>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          <div className=\"flex flex-wrap gap-2\">\n            <Button\n              variant={aiProvider === \"webllm\" ? \"default\" : \"outline\"}\n              onClick={() => handleProviderChange(\"webllm\")}\n              className=\"flex items-center gap-2\"\n            >\n              <Cpu className=\"h-4 w-4\" />\n              Local (WebLLM)\n            </Button>\n            <Button\n              variant={aiProvider === \"openai\" ? \"default\" : \"outline\"}\n              onClick={() => handleProviderChange(\"openai\")}\n              className=\"flex items-center gap-2\"\n            >\n              <Cloud className=\"h-4 w-4\" />\n              OpenAI\n            </Button>\n            <Button\n              variant={aiProvider === \"anthropic\" ? \"default\" : \"outline\"}\n              onClick={() => handleProviderChange(\"anthropic\")}\n              className=\"flex items-center gap-2\"\n            >\n              <Cloud className=\"h-4 w-4\" />\n              Anthropic\n            </Button>\n            <Button\n              variant={aiProvider === \"openai-compatible\" ? \"default\" : \"outline\"}\n              onClick={() => handleProviderChange(\"openai-compatible\")}\n              className=\"flex items-center gap-2\"\n            >\n              <Server className=\"h-4 w-4\" />\n              OpenAI-Compatible\n            </Button>\n          </div>\n\n          {aiProvider === \"webllm\" && (\n            <Alert>\n              <Zap className=\"h-4 w-4\" />\n              <AlertDescription>\n                <strong>100% Local AI</strong> - Models run entirely in your browser.\n              </AlertDescription>\n            </Alert>\n          )}\n\n          {aiProvider === \"openai\" && (\n            <div className=\"space-y-4 pt-2\">\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"openai-key\" className=\"flex items-center gap-2\">\n                  <Key className=\"h-4 w-4\" />\n                  OpenAI API Key\n                </Label>\n                <div className=\"flex gap-2\">\n                  <div className=\"relative flex-1\">\n                    <Input\n                      id=\"openai-key\"\n                      type={showApiKey.openai ? \"text\" : \"password\"}\n                      placeholder=\"sk-...\"\n                      value={apiKeyInputs.openai || \"\"}\n                      onChange={(e) => handleApiKeyChange(\"openai\", e.target.value)}\n                    />\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      className=\"absolute right-0 top-0 h-full\"\n                      onClick={() => setShowApiKey((prev) => ({ ...prev, openai: !prev.openai }))}\n                    >\n                      {showApiKey.openai ? (\n                        <EyeOff className=\"h-4 w-4\" />\n                      ) : (\n                        <Eye className=\"h-4 w-4\" />\n                      )}\n                    </Button>\n                  </div>\n                  <Button\n                    onClick={() => handleSaveApiKey(\"openai\")}\n                    disabled={isTesting || !apiKeyInputs.openai}\n                  >\n                    {isTesting ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : \"Save\"}\n                  </Button>\n                </div>\n              </div>\n              <div className=\"space-y-2\">\n                <Label>Model</Label>\n                <Select\n                  value={providerConfigs.openai?.modelId || \"gpt-4o-mini\"}\n                  onValueChange={(value) => handleModelChange(\"openai\", value)}\n                >\n                  <SelectTrigger>\n                    <SelectValue />\n                  </SelectTrigger>\n                  <SelectContent>\n                    {OPENAI_MODELS.map((model) => (\n                      <SelectItem key={model.id} value={model.id}>\n                        {model.name} - {model.description}\n                      </SelectItem>\n                    ))}\n                  </SelectContent>\n                </Select>\n              </div>\n              {providerConfigs.openai?.apiKey && (\n                <Badge variant=\"secondary\" className=\"bg-green-500/10 text-green-600\">\n                  <Check className=\"h-3 w-3 mr-1\" />\n                  API Key Configured\n                </Badge>\n              )}\n            </div>\n          )}\n\n          {aiProvider === \"anthropic\" && (\n            <div className=\"space-y-4 pt-2\">\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"anthropic-key\" className=\"flex items-center gap-2\">\n                  <Key className=\"h-4 w-4\" />\n                  Anthropic API Key\n                </Label>\n                <div className=\"flex gap-2\">\n                  <div className=\"relative flex-1\">\n                    <Input\n                      id=\"anthropic-key\"\n                      type={showApiKey.anthropic ? \"text\" : \"password\"}\n                      placeholder=\"sk-ant-...\"\n                      value={apiKeyInputs.anthropic || \"\"}\n                      onChange={(e) => handleApiKeyChange(\"anthropic\", e.target.value)}\n                    />\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      className=\"absolute right-0 top-0 h-full\"\n                      onClick={() =>\n                        setShowApiKey((prev) => ({ ...prev, anthropic: !prev.anthropic }))\n                      }\n                    >\n                      {showApiKey.anthropic ? (\n                        <EyeOff className=\"h-4 w-4\" />\n                      ) : (\n                        <Eye className=\"h-4 w-4\" />\n                      )}\n                    </Button>\n                  </div>\n                  <Button\n                    onClick={() => handleSaveApiKey(\"anthropic\")}\n                    disabled={isTesting || !apiKeyInputs.anthropic}\n                  >\n                    {isTesting ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : \"Save\"}\n                  </Button>\n                </div>\n              </div>\n              <div className=\"space-y-2\">\n                <Label>Model</Label>\n                <Select\n                  value={providerConfigs.anthropic?.modelId || \"claude-sonnet-4-20250514\"}\n                  onValueChange={(value) => handleModelChange(\"anthropic\", value)}\n                >\n                  <SelectTrigger>\n                    <SelectValue />\n                  </SelectTrigger>\n                  <SelectContent>\n                    {ANTHROPIC_MODELS.map((model) => (\n                      <SelectItem key={model.id} value={model.id}>\n                        {model.name} - {model.description}\n                      </SelectItem>\n                    ))}\n                  </SelectContent>\n                </Select>\n              </div>\n              {providerConfigs.anthropic?.apiKey && (\n                <Badge variant=\"secondary\" className=\"bg-green-500/10 text-green-600\">\n                  <Check className=\"h-3 w-3 mr-1\" />\n                  API Key Configured\n                </Badge>\n              )}\n            </div>\n          )}\n\n          {aiProvider === \"openai-compatible\" && (\n            <div className=\"space-y-4 pt-2\">\n              <Alert>\n                <Server className=\"h-4 w-4\" />\n                <AlertDescription>\n                  <strong>OpenAI-Compatible API</strong> - Connect to Ollama, LocalAI, vLLM,\n                  DeepSeek, and other services that implement the OpenAI chat completions API.\n                </AlertDescription>\n              </Alert>\n\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"compatible-base-url\" className=\"flex items-center gap-2\">\n                  <Link className=\"h-4 w-4\" />\n                  Base URL <span className=\"text-destructive\">*</span>\n                </Label>\n                <Input\n                  id=\"compatible-base-url\"\n                  type=\"url\"\n                  placeholder=\"http://localhost:11434/v1\"\n                  value={compatibleBaseUrl}\n                  onChange={(e) => setCompatibleBaseUrl(e.target.value)}\n                />\n                <p className=\"text-xs text-muted-foreground\">\n                  The API endpoint URL (e.g., http://localhost:11434/v1 for Ollama)\n                </p>\n              </div>\n\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"compatible-model-id\" className=\"flex items-center gap-2\">\n                  <Cpu className=\"h-4 w-4\" />\n                  Model ID <span className=\"text-destructive\">*</span>\n                </Label>\n                <Input\n                  id=\"compatible-model-id\"\n                  type=\"text\"\n                  placeholder=\"llama3.2\"\n                  value={compatibleModelId}\n                  onChange={(e) => setCompatibleModelId(e.target.value)}\n                />\n                <p className=\"text-xs text-muted-foreground\">\n                  The model name as recognized by your API (e.g., llama3.2, deepseek-coder)\n                </p>\n              </div>\n\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"compatible-api-key\" className=\"flex items-center gap-2\">\n                  <Key className=\"h-4 w-4\" />\n                  API Key <span className=\"text-muted-foreground text-xs\">(optional)</span>\n                </Label>\n                <div className=\"relative\">\n                  <Input\n                    id=\"compatible-api-key\"\n                    type={showApiKey[\"openai-compatible\"] ? \"text\" : \"password\"}\n                    placeholder=\"Optional - only if your server requires authentication\"\n                    value={compatibleApiKey}\n                    onChange={(e) => setCompatibleApiKey(e.target.value)}\n                  />\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"absolute right-0 top-0 h-full\"\n                    onClick={() =>\n                      setShowApiKey((prev) => ({\n                        ...prev,\n                        \"openai-compatible\": !prev[\"openai-compatible\"],\n                      }))\n                    }\n                  >\n                    {showApiKey[\"openai-compatible\"] ? (\n                      <EyeOff className=\"h-4 w-4\" />\n                    ) : (\n                      <Eye className=\"h-4 w-4\" />\n                    )}\n                  </Button>\n                </div>\n              </div>\n\n              <Button\n                onClick={handleSaveCompatibleConfig}\n                disabled={isTesting || !compatibleBaseUrl || !compatibleModelId}\n                className=\"w-full\"\n              >\n                {isTesting ? <Loader2 className=\"h-4 w-4 animate-spin mr-2\" /> : null}\n                {isTesting ? \"Testing Connection...\" : \"Test & Save\"}\n              </Button>\n\n              {providerConfigs[\"openai-compatible\"]?.baseUrl &&\n                providerConfigs[\"openai-compatible\"]?.modelId && (\n                  <Badge variant=\"secondary\" className=\"bg-green-500/10 text-green-600\">\n                    <Check className=\"h-3 w-3 mr-1\" />\n                    Configured: {providerConfigs[\"openai-compatible\"].modelId}\n                  </Badge>\n                )}\n            </div>\n          )}\n        </CardContent>\n      </Card>\n\n      {/* Download Progress - Only show for WebLLM */}\n      {aiProvider === \"webllm\" && isDownloading && (\n        <Card>\n          <CardContent className=\"pt-6\">\n            <div className=\"space-y-3\">\n              <div className=\"flex items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <Loader2 className=\"h-4 w-4 animate-spin text-primary\" />\n                  <span className=\"font-medium\">\n                    {modelStatus === \"downloading\" ? \"Downloading model...\" : \"Loading model...\"}\n                  </span>\n                </div>\n                <span className=\"text-sm text-muted-foreground\">{downloadProgress}%</span>\n              </div>\n              <Progress value={downloadProgress} />\n              <p className=\"text-xs text-muted-foreground\">{downloadStatus}</p>\n            </div>\n          </CardContent>\n        </Card>\n      )}\n\n      {/* Error Display */}\n      {modelStatus === \"error\" && error && (\n        <Alert variant=\"destructive\">\n          <AlertCircle className=\"h-4 w-4\" />\n          <AlertDescription>{error}</AlertDescription>\n        </Alert>\n      )}\n\n      {/* Available Models - Only for WebLLM */}\n      {aiProvider === \"webllm\" && (\n        <Card>\n          <CardHeader>\n            <CardTitle>Available Local Models</CardTitle>\n            <CardDescription>Select a model to use with Duck Brain.</CardDescription>\n          </CardHeader>\n          <CardContent>\n            <div className=\"grid gap-4\">\n              {AVAILABLE_MODELS.map((model) => {\n                const status = getModelStatus(model);\n                const isCurrentlyDownloading = isDownloading && currentModel === model.id;\n\n                return (\n                  <div\n                    key={model.id}\n                    className={`flex items-start justify-between p-4 rounded-lg border ${\n                      status === \"ready\" ? \"border-green-500/50 bg-green-500/5\" : \"border-border\"\n                    }`}\n                  >\n                    <div className=\"space-y-1 flex-1\">\n                      <div className=\"flex items-center gap-2\">\n                        <Cpu className=\"h-4 w-4 text-muted-foreground\" />\n                        <span className=\"font-medium\">{model.displayName}</span>\n                        {status === \"ready\" && (\n                          <Badge variant=\"secondary\" className=\"bg-green-500/10 text-green-600\">\n                            <Check className=\"h-3 w-3 mr-1\" />\n                            Active\n                          </Badge>\n                        )}\n                      </div>\n                      <p className=\"text-sm text-muted-foreground\">{model.description}</p>\n                      <div className=\"flex items-center gap-4 mt-2\">\n                        <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n                          <HardDrive className=\"h-3 w-3\" />\n                          {model.size}\n                        </div>\n                        <div className=\"text-xs text-muted-foreground\">\n                          Context: {model.contextLength.toLocaleString()} tokens\n                        </div>\n                      </div>\n                    </div>\n\n                    <div className=\"flex items-center gap-2 ml-4\">\n                      {status === \"ready\" ? (\n                        <Button variant=\"outline\" size=\"sm\" disabled>\n                          <Check className=\"h-4 w-4 mr-1\" />\n                          Loaded\n                        </Button>\n                      ) : isCurrentlyDownloading ? (\n                        <Button variant=\"outline\" size=\"sm\" disabled>\n                          <Loader2 className=\"h-4 w-4 mr-1 animate-spin\" />\n                          Loading...\n                        </Button>\n                      ) : (\n                        <Button\n                          variant=\"outline\"\n                          size=\"sm\"\n                          onClick={() => handleLoadModel(model.id)}\n                          disabled={isDownloading || isWebGPUSupported === false}\n                        >\n                          <Download className=\"h-4 w-4 mr-1\" />\n                          Load\n                        </Button>\n                      )}\n                    </div>\n                  </div>\n                );\n              })}\n            </div>\n          </CardContent>\n        </Card>\n      )}\n\n      {/* Storage Info - Only for WebLLM */}\n      {aiProvider === \"webllm\" && (\n        <Card>\n          <CardHeader>\n            <div className=\"flex items-center justify-between\">\n              <CardTitle className=\"text-base\">Storage Information</CardTitle>\n              {cacheSize && (\n                <Badge variant=\"secondary\" className=\"font-mono\">\n                  {cacheSize} used\n                </Badge>\n              )}\n            </div>\n          </CardHeader>\n          <CardContent>\n            <div className=\"text-sm text-muted-foreground space-y-3\">\n              <p>Models are cached in your browser's IndexedDB storage.</p>\n              <div className=\"flex items-center justify-between pt-2 border-t\">\n                <p className=\"text-xs\">Clear all cached model data to free up storage.</p>\n                <Button\n                  variant=\"destructive\"\n                  size=\"sm\"\n                  onClick={handleClearCache}\n                  disabled={isClearing}\n                >\n                  {isClearing ? (\n                    <Loader2 className=\"h-4 w-4 mr-2 animate-spin\" />\n                  ) : (\n                    <Trash2 className=\"h-4 w-4 mr-2\" />\n                  )}\n                  Clear Cache\n                </Button>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n      )}\n    </div>\n  );\n};\n\nexport default BrainTab;\n"
  },
  {
    "path": "src/components/workspace/ConnectionsTab.tsx",
    "content": "import { useState } from \"react\";\nimport { useDuckStore, ConnectionProvider } from \"@/store\";\nimport { generateUUID } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport { Plus, Edit2, Trash2, Database, ExternalLink, InfoIcon } from \"lucide-react\";\nimport ConnectionManager from \"@/components/connection/ConnectionsModal\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport * as z from \"zod\";\n\nconst scopeEnum = z.enum([\"External\", \"OPFS\"]);\nconst nameSchema = z\n  .string()\n  .min(2, { message: \"Connection name must be at least 2 characters.\" })\n  .max(30, { message: \"Connection name must not exceed 30 characters.\" });\n\nconst opfsSchema = z.object({\n  name: nameSchema,\n  scope: z.literal(scopeEnum.enum.OPFS),\n  path: z.string().min(1, { message: \"Path is required.\" }),\n});\n\nconst externalSchema = z.object({\n  name: nameSchema,\n  scope: z.literal(scopeEnum.enum.External),\n  host: z.string().url({ message: \"Host must be a valid URL.\" }),\n  port: z\n    .string()\n    .refine((val) => !isNaN(parseInt(val, 10)) || val === \"\", {\n      message: \"Port must be a number.\",\n    })\n    .optional(),\n  database: z.string().optional(),\n  user: z.string().optional(),\n  password: z.string().optional(),\n  authMode: z.enum([\"none\", \"password\", \"api_key\"]).optional(),\n  apiKey: z.string().optional(),\n});\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst connectionSchema = z.discriminatedUnion(\"scope\", [opfsSchema, externalSchema]);\n\ntype ConnectionFormValues = z.infer<typeof connectionSchema>;\n\nconst ConnectionsTab = () => {\n  const connectionList = useDuckStore((s) => s.connectionList);\n  const addConnection = useDuckStore((s) => s.addConnection);\n  const updateConnection = useDuckStore((s) => s.updateConnection);\n  const deleteConnection = useDuckStore((s) => s.deleteConnection);\n  const getConnection = useDuckStore((s) => s.getConnection);\n  const setCurrentConnection = useDuckStore((s) => s.setCurrentConnection);\n  const currentConnection = useDuckStore((s) => s.currentConnection);\n  const isLoadingExternalConnection = useDuckStore((s) => s.isLoadingExternalConnection);\n  const isLoading = useDuckStore((s) => s.isLoading);\n\n  const [isEditing, setIsEditing] = useState(false);\n  const [editingConnectionId, setEditingConnectionId] = useState<string | null>(null);\n  const [deleteConfirmationId, setDeleteConfirmationId] = useState<string | null>(null);\n  const [isAddConnectionDialogOpen, setIsAddConnectionDialogOpen] = useState(false);\n  const [editingConnection, setEditingConnection] = useState<ConnectionFormValues | undefined>(\n    undefined\n  );\n\n  const handleAddConnection = async (values: ConnectionFormValues) => {\n    const connectionData: ConnectionProvider = {\n      ...values,\n      id: generateUUID(),\n      port: values.scope === \"External\" && values.port ? parseInt(values.port, 10) : undefined,\n      environment: \"APP\",\n    };\n    await addConnection(connectionData);\n  };\n\n  const handleUpdateConnection = async (values: ConnectionFormValues): Promise<void> => {\n    if (!editingConnectionId) return;\n\n    const connectionData: ConnectionProvider = {\n      ...values,\n      id: editingConnectionId,\n      port: values.scope === \"External\" && values.port ? parseInt(values.port, 10) : undefined,\n      environment: \"APP\",\n    };\n    updateConnection(connectionData);\n    setEditingConnectionId(null);\n    setIsEditing(false);\n  };\n\n  const handleConnect = async (connectionId: string) => {\n    try {\n      await setCurrentConnection(connectionId);\n    } catch (error) {\n      console.error(\"Failed to connect:\", error);\n    }\n  };\n\n  const onEdit = (connectionId: string) => {\n    const connection = getConnection(connectionId);\n    if (connection) {\n      setEditingConnectionId(connectionId);\n      const baseConnection = {\n        name: connection.name,\n        scope: connection.scope as \"External\" | \"OPFS\",\n      };\n\n      if (connection.scope === \"OPFS\") {\n        setEditingConnection({\n          ...baseConnection,\n          scope: \"OPFS\",\n          path: connection.path || \"\",\n        });\n      } else {\n        setEditingConnection({\n          ...baseConnection,\n          scope: \"External\",\n          host: connection.host || \"\",\n          port: connection.port?.toString() || \"\",\n          database: connection.database,\n          user: connection.user,\n          password: connection.password,\n          authMode: connection.authMode,\n          apiKey: connection.apiKey,\n        });\n      }\n      setIsEditing(true);\n    }\n  };\n\n  return (\n    <div className=\"p-4 space-y-6 overflow-auto h-full\">\n      {/* Add Connection Button */}\n      <div className=\"flex justify-end\">\n        <Button\n          onClick={() => setIsAddConnectionDialogOpen(true)}\n          className=\"flex items-center gap-2\"\n          variant=\"outline\"\n          disabled={isLoadingExternalConnection}\n        >\n          <Plus className=\"h-4 w-4\" />\n          Add Connection\n        </Button>\n      </div>\n\n      <ConnectionManager\n        open={isAddConnectionDialogOpen}\n        onOpenChange={setIsAddConnectionDialogOpen}\n        onSubmit={handleAddConnection}\n        isEditMode={false}\n      />\n\n      <ConnectionManager\n        open={isEditing}\n        onOpenChange={(open) => {\n          setIsEditing(open);\n          if (!open) {\n            setEditingConnectionId(null);\n            setEditingConnection(undefined);\n          }\n        }}\n        onSubmit={handleUpdateConnection}\n        initialValues={editingConnection}\n        isEditMode={true}\n      />\n\n      <Card>\n        <CardHeader>\n          <CardTitle>Available Connections</CardTitle>\n          <CardDescription>List of all configured database connections</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <ScrollArea className=\"h-[calc(100vh-400px)]\">\n            <Table>\n              <TableHeader>\n                <TableRow>\n                  <TableHead>Name</TableHead>\n                  <TableHead>Scope</TableHead>\n                  <TableHead>Host</TableHead>\n                  <TableHead>Database</TableHead>\n                  <TableHead>Environment</TableHead>\n                  <TableHead className=\"text-right\">Actions</TableHead>\n                </TableRow>\n              </TableHeader>\n              <TableBody>\n                {connectionList.connections.map((connection) => (\n                  <TableRow key={connection.id}>\n                    <TableCell\n                      className={\n                        connection.id === currentConnection?.id ? \"border-l-4 border-green-500\" : \"\"\n                      }\n                    >\n                      <div className=\"flex items-center gap-2\">\n                        {connection.scope === \"WASM\" ? (\n                          <Database size={16} />\n                        ) : (\n                          <ExternalLink size={16} />\n                        )}\n                        {connection.name}\n                      </div>\n                    </TableCell>\n                    <TableCell>{connection.scope}</TableCell>\n                    <TableCell>\n                      {connection.host || (connection.scope === \"WASM\" ? \"Local\" : \"-\")}\n                    </TableCell>\n                    <TableCell>\n                      {connection.database ||\n                        (connection.scope === \"WASM\" ? \"memory\" : connection.database)}\n                    </TableCell>\n                    <TableCell>{connection.environment}</TableCell>\n\n                    <TableCell className=\"text-right\">\n                      <div className=\"flex justify-end gap-2 items-center\">\n                        {connection.environment !== \"BUILT_IN\" && (\n                          <Button\n                            variant=\"outline\"\n                            size=\"sm\"\n                            disabled={connection.id === currentConnection?.id || isLoading}\n                            onClick={() => handleConnect(connection.id)}\n                          >\n                            {connection.id === currentConnection?.id ? \"Connected\" : \"Connect\"}\n                          </Button>\n                        )}\n\n                        {connection.environment === \"BUILT_IN\" ||\n                        connection.environment === \"ENV\" ? (\n                          <TooltipProvider>\n                            <Tooltip>\n                              <TooltipTrigger asChild>\n                                <InfoIcon size={18} className=\"text-muted-foreground\" />\n                              </TooltipTrigger>\n                              <TooltipContent>\n                                <p className=\"text-sm max-w-[220px] !text-center\">\n                                  {connection.environment === \"ENV\"\n                                    ? \"Configured via environment variables — cannot be edited or deleted.\"\n                                    : \"Built-in connection — cannot be edited or deleted.\"}\n                                </p>\n                              </TooltipContent>\n                            </Tooltip>\n                          </TooltipProvider>\n                        ) : (\n                          <>\n                            <Button\n                              variant=\"ghost\"\n                              size=\"sm\"\n                              onClick={() => onEdit(connection.id)}\n                              disabled={connection.id === \"WASM\"}\n                            >\n                              <Edit2 size={16} />\n                            </Button>\n                            <AlertDialog\n                              open={deleteConfirmationId === connection.id}\n                              onOpenChange={(isOpen) =>\n                                setDeleteConfirmationId(isOpen ? connection.id : null)\n                              }\n                            >\n                              <AlertDialogTrigger asChild>\n                                <Button variant=\"ghost\" size=\"sm\" disabled={isLoading}>\n                                  <Trash2 size={16} className=\"text-destructive\" />\n                                </Button>\n                              </AlertDialogTrigger>\n                              <AlertDialogContent>\n                                <AlertDialogHeader>\n                                  <AlertDialogTitle>Delete Connection</AlertDialogTitle>\n                                  <AlertDialogDescription>\n                                    Are you sure you want to delete the connection \"\n                                    {connection.name}\"? This action cannot be undone.\n                                  </AlertDialogDescription>\n                                </AlertDialogHeader>\n                                <AlertDialogFooter>\n                                  <AlertDialogCancel>Cancel</AlertDialogCancel>\n                                  <AlertDialogAction\n                                    onClick={() => {\n                                      deleteConnection(connection.id);\n                                      setDeleteConfirmationId(null);\n                                    }}\n                                    className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n                                  >\n                                    Delete\n                                  </AlertDialogAction>\n                                </AlertDialogFooter>\n                              </AlertDialogContent>\n                            </AlertDialog>\n                          </>\n                        )}\n                      </div>\n                    </TableCell>\n                  </TableRow>\n                ))}\n              </TableBody>\n            </Table>\n          </ScrollArea>\n        </CardContent>\n      </Card>\n    </div>\n  );\n};\n\nexport default ConnectionsTab;\n"
  },
  {
    "path": "src/components/workspace/ExplainPlanViewer.tsx",
    "content": "import { useMemo } from \"react\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetDescription,\n} from \"@/components/ui/sheet\";\n\ninterface ExplainPlanViewerProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  explainText: string;\n}\n\nconst OPERATOR_COLORS: Record<string, string> = {\n  SEQ_SCAN: \"text-blue-500\",\n  INDEX_SCAN: \"text-green-500\",\n  HASH_JOIN: \"text-purple-500\",\n  NESTED_LOOP_JOIN: \"text-purple-400\",\n  HASH_GROUP_BY: \"text-orange-500\",\n  PERFECT_HASH_GROUP_BY: \"text-orange-400\",\n  UNGROUPED_AGGREGATE: \"text-orange-300\",\n  ORDER_BY: \"text-yellow-500\",\n  TOP_N: \"text-yellow-400\",\n  FILTER: \"text-red-400\",\n  PROJECTION: \"text-cyan-500\",\n  TABLE_SCAN: \"text-blue-400\",\n  CHUNK_SCAN: \"text-blue-300\",\n  RESULT_COLLECTOR: \"text-gray-400\",\n  EXPLAIN_ANALYZE: \"text-gray-400\",\n  LIMIT: \"text-pink-400\",\n  CROSS_PRODUCT: \"text-red-500\",\n};\n\nfunction colorizeOperators(text: string): React.ReactNode[] {\n  const lines = text.split(\"\\n\");\n  return lines.map((line, i) => {\n    let colorClass: string | null = null;\n    for (const [key, color] of Object.entries(OPERATOR_COLORS)) {\n      if (line.toUpperCase().includes(key)) {\n        colorClass = color;\n        break;\n      }\n    }\n    return (\n      <span key={i} className={colorClass ?? undefined}>\n        {line}\n        {\"\\n\"}\n      </span>\n    );\n  });\n}\n\nexport function ExplainPlanViewer({ open, onOpenChange, explainText }: ExplainPlanViewerProps) {\n  const colorized = useMemo(() => colorizeOperators(explainText), [explainText]);\n\n  return (\n    <Sheet open={open} onOpenChange={onOpenChange}>\n      <SheetContent side=\"right\" className=\"w-[600px] sm:max-w-[600px] p-0 flex flex-col\">\n        <SheetHeader className=\"px-6 py-4 border-b shrink-0\">\n          <SheetTitle>Explain Analyze</SheetTitle>\n          <SheetDescription>Query execution plan from DuckDB</SheetDescription>\n        </SheetHeader>\n        <ScrollArea className=\"flex-1 min-h-0\">\n          <pre className=\"p-4 font-mono text-xs leading-relaxed whitespace-pre overflow-x-auto\">\n            {explainText.trim() ? colorized : \"No explain plan available\"}\n          </pre>\n        </ScrollArea>\n      </SheetContent>\n    </Sheet>\n  );\n}\n"
  },
  {
    "path": "src/components/workspace/HomeTab.tsx",
    "content": "import { useState, useEffect } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardHeader, CardTitle, CardDescription, CardFooter } from \"@/components/ui/card\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport {\n  Github,\n  Terminal,\n  BookOpen,\n  Database,\n  ExternalLink,\n  Loader2,\n  TestTubeDiagonal,\n  Server,\n  Building2,\n  BarChart3,\n  Bookmark,\n  Logs,\n  PackageCheck,\n} from \"lucide-react\";\nimport { useDuckStore } from \"@/store\";\nimport { motion } from \"framer-motion\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport Logo from \"/logo.png\";\nimport LogoLight from \"/logo-light.png\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport {\n  getSavedQueries,\n  type SavedQuery,\n} from \"@/services/persistence/repositories/savedQueryRepository\";\n\nconst quickStartActions = [\n  {\n    title: \"SQL Query\",\n    icon: <Terminal className=\"w-5 h-5\" />,\n    description: \"Write and execute SQL queries on Duck DB Wasm!\",\n    action: \"sql\",\n  },\n  {\n    title: \"Explore with Examples\",\n    icon: <TestTubeDiagonal className=\"w-5 h-5\" />,\n    description: \"Explore example query set to feel the power of DuckDB.\",\n    action: \"examples\",\n  },\n  {\n    title: \"Connect Local DuckDB\",\n    icon: <Server className=\"w-5 h-5\" />,\n    description: \"Query your own DuckDB instance via HTTP server extension.\",\n    action: \"connect\",\n  },\n  {\n    title: \"NEW! DuckUI Embed\",\n    icon: <PackageCheck className=\"w-5 h-5\" />,\n    description: \"Embed DuckUI in your own applications.\",\n    link: \"https://duckui.com/play/\",\n  },\n];\n\nconst resourceCards = [\n  {\n    title: \"Star us on GitHub!\",\n    description: \"Support our project by starring it on GitHub.\",\n    link: \"https://github.com/caioricciuti/duck-ui\",\n    Icon: Github,\n    action: \"Star on GitHub\",\n  },\n  {\n    title: \"DuckDB Docs\",\n    description: \"Explore DuckDB documentation and learn more.\",\n    Icon: BookOpen,\n    link: \"https://duckdb.org/docs/\",\n    action: \"Read Docs\",\n  },\n  {\n    title: \"Duck-UI Documentation\",\n    Icon: ExternalLink,\n    description: \"Learn how to make the most of Duck-UI.\",\n    link: \"https://duckui.com/\",\n    action: \"Learn More\",\n  },\n];\n\nconst caioRicciutiProducts = [\n  {\n    title: \"Ibero Data\",\n    description: \"Data engineering & analytics solutions\",\n    link: \"https://iberodata.es?utm_source=duck-ui&utm_medium=app&utm_campaign=cross-promo\",\n    Icon: Building2,\n  },\n  {\n    title: \"CH-UI\",\n    description: \"Modern UI for ClickHouse databases\",\n    link: \"https://ch-ui.com?utm_source=duck-ui&utm_medium=app&utm_campaign=cross-promo\",\n    Icon: Database,\n  },\n  {\n    title: \"Etiquetta\",\n    description: \"Simple, privacy-friendly web analytics\",\n    link: \"https://github.com/caioricciuti/etiquetta\",\n    Icon: BarChart3,\n  },\n  {\n    title: \"Dev Cockpit\",\n    description: \"Get Under the Hood of Your Apple Silicon.\",\n    link: \"https://devcockpit.app?utm_source=duck-ui&utm_medium=app&utm_campaign=cross-promo\",\n    Icon: Logs,\n  },\n];\n\nconst HomeTab = () => {\n  const createTab = useDuckStore((s) => s.createTab);\n  const queryHistory = useDuckStore((s) => s.queryHistory);\n  const error = useDuckStore((s) => s.error);\n  const tabs = useDuckStore((s) => s.tabs);\n  const setActiveTab = useDuckStore((s) => s.setActiveTab);\n  const currentProfile = useDuckStore((s) => s.currentProfile);\n  const currentProfileId = useDuckStore((s) => s.currentProfileId);\n  const savedQueriesVersion = useDuckStore((s) => s.savedQueriesVersion);\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const [recentItems, setRecentItems] = useState<any[]>([]);\n  const [loading, setLoading] = useState(false);\n  const [savedQueries, setSavedQueries] = useState<SavedQuery[]>([]);\n  const [savedLoading, setSavedLoading] = useState(false);\n\n  useEffect(() => {\n    getUsersRecentItems();\n  }, []);\n\n  useEffect(() => {\n    if (!currentProfileId) return;\n    setSavedLoading(true);\n    getSavedQueries(currentProfileId)\n      .then(setSavedQueries)\n      .catch(console.error)\n      .finally(() => setSavedLoading(false));\n  }, [currentProfileId, savedQueriesVersion]);\n\n  const formatDate = (dateString: string) => {\n    const date = new Date(dateString);\n    return new Intl.DateTimeFormat(\"en-US\", {\n      day: \"numeric\",\n      month: \"short\",\n      year: \"numeric\",\n      hour: \"2-digit\",\n      minute: \"2-digit\",\n    }).format(date);\n  };\n\n  // Helper to open or focus a singleton tab (same pattern as Sidebar)\n  const openOrFocusTab = (type: \"connections\" | \"brain\", title: string) => {\n    const existing = tabs.find((t) => t.type === type);\n    if (existing) {\n      setActiveTab(existing.id);\n    } else {\n      createTab(type, \"\", title);\n    }\n  };\n\n  const handleNewAction = (type: string, query?: string) => {\n    if (type === \"sql\") {\n      createTab(\"sql\", query);\n    }\n    if (type === \"examples\") {\n      createTab(\n        \"sql\",\n        `\nSELECT * FROM 'https://blobs.duckdb.org/stations.parquet' LIMIT 1000;\n`,\n        \"Duck UI Explore\"\n      );\n    }\n    if (type === \"connect\") {\n      // Open Connections tab instead of modal\n      openOrFocusTab(\"connections\", \"Connections\");\n    }\n  };\n\n  const getUsersRecentItems = async () => {\n    setLoading(true);\n    try {\n      const recentQueries = await Promise.resolve(\n        queryHistory.slice(0, 6).map((h) => ({\n          cleaned_query: h.query,\n          latest_event_time: h.timestamp,\n          query_kind: \"query\",\n        }))\n      );\n      setRecentItems(recentQueries);\n    } catch (err) {\n      console.error(err);\n    } finally {\n      setLoading(false);\n    }\n  };\n  const truncateQuery = (query: string, length: number = 50) => {\n    return query.length > length ? `${query.slice(0, length)}...` : query;\n  };\n\n  const duck_ui_version = __DUCK_UI_VERSION__ || \"Error loading version\";\n  const duck_ui_release_date = __DUCK_UI_RELEASE_DATE__ || \"N/A\";\n\n  const { theme } = useTheme();\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <div className=\"p-8 space-y-10 w-full max-w-[1400px] mx-auto overflow-auto flex-1\">\n        <motion.div\n          initial={{ opacity: 0, y: -20 }}\n          animate={{ opacity: 1, y: 0 }}\n          className=\"space-y-3 flex items-center space-x-4\"\n        >\n          <img src={theme === \"dark\" ? Logo : LogoLight} alt=\"Logo\" className=\"h-12\" />\n          <h1 className=\"text-4xl font-bold tracking-tight\">\n            {currentProfile ? `Welcome, ${currentProfile.name}` : \"Welcome to Duck-UI\"}\n          </h1>\n        </motion.div>\n\n        <motion.div\n          initial={{ opacity: 0, y: 20 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ delay: 0.1 }}\n          className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\"\n        >\n          {quickStartActions.map((action, index) => (\n            <motion.div\n              key={index}\n              initial={{ opacity: 0, scale: 0.9 }}\n              animate={{ opacity: 1, scale: 1 }}\n              transition={{ delay: index * 0.1 }}\n              className=\"truncate\"\n            >\n              <Button\n                variant=\"outline\"\n                className=\"h-auto p-6 flex flex-col items-start space-y-3 hover:bg-accent hover:text-accent-foreground group w-full border-2\"\n                onClick={() =>\n                  action.action\n                    ? handleNewAction(action.action)\n                    : action.link && window.open(action.link, \"_blank\")\n                }\n              >\n                <div className=\"flex items-center space-x-3 text-primary\">\n                  <div className=\"p-3 rounded-lg bg-primary/10 group-hover:bg-primary/20 transition-colors\">\n                    {action.icon}\n                  </div>\n                  <p className=\"font-bold text-lg truncate\">{action.title}</p>\n                </div>\n                <p className=\"text-sm text-muted-foreground w-full truncate\">\n                  {action.description}\n                </p>\n              </Button>\n            </motion.div>\n          ))}\n        </motion.div>\n\n        <Tabs defaultValue=\"recent\" className=\"space-y-6\">\n          <TabsList className=\"h-11\">\n            <TabsTrigger\n              value=\"recent\"\n              className=\"flex items-center gap-2 data-[state=active]:text-primary px-6\"\n            >\n              Recent Queries\n              {loading && <Loader2 className=\"w-3 h-3 animate-spin\" />}\n            </TabsTrigger>\n            <TabsTrigger\n              value=\"saved\"\n              className=\"flex items-center gap-2 data-[state=active]:text-primary px-6\"\n            >\n              <Bookmark className=\"w-3 h-3\" />\n              Saved Queries\n            </TabsTrigger>\n            <TabsTrigger value=\"resources\" className=\"data-[state=active]:text-primary px-6\">\n              Resources\n            </TabsTrigger>\n            <TabsTrigger value=\"iberodata\" className=\"data-[state=active]:text-primary px-6\">\n              Caio Ricciuti\n            </TabsTrigger>\n          </TabsList>\n\n          <TabsContent value=\"recent\" className=\"space-y-6\">\n            {loading ? (\n              <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5\">\n                {[1, 2, 3].map((i) => (\n                  <Card key={i} className=\"space-y-2\">\n                    <CardHeader>\n                      <Skeleton className=\"h-4 w-[250px]\" />\n                      <Skeleton className=\"h-4 w-[200px]\" />\n                    </CardHeader>\n                    <CardFooter>\n                      <Skeleton className=\"h-4 w-[150px]\" />\n                    </CardFooter>\n                  </Card>\n                ))}\n              </div>\n            ) : error ? (\n              <Card className=\"p-4 text-center text-muted-foreground\">{error}</Card>\n            ) : recentItems.length === 0 ? (\n              <Card className=\"p-8 text-center text-muted-foreground border-dashed\">\n                No recent queries found\n              </Card>\n            ) : (\n              <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5\">\n                {recentItems.map((item, index) => (\n                  <motion.div\n                    key={index}\n                    initial={{ opacity: 0, y: 20 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    transition={{ delay: index * 0.05 }}\n                  >\n                    <Card\n                      className=\"hover:bg-accent/50 cursor-pointer transition-colors\"\n                      onClick={() => handleNewAction(\"sql\", item.cleaned_query)}\n                    >\n                      <CardHeader>\n                        <CardTitle className=\"text-sm font-medium flex items-center space-x-2\">\n                          <Database className=\"w-4 h-4 text-muted-foreground\" />\n                          <span className=\"text-muted-foreground\">\n                            {item.query_kind || \"Query\"}\n                          </span>\n                        </CardTitle>\n                        <CardDescription className=\"text-xs font-mono text-muted-foreground truncate\">\n                          {truncateQuery(item.cleaned_query)}\n                        </CardDescription>\n                      </CardHeader>\n                      <CardFooter className=\"text-xs text-muted-foreground\">\n                        {formatDate(item.latest_event_time)}\n                      </CardFooter>\n                    </Card>\n                  </motion.div>\n                ))}\n              </div>\n            )}\n          </TabsContent>\n\n          <TabsContent value=\"saved\" className=\"space-y-6\">\n            {savedLoading ? (\n              <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5\">\n                {[1, 2, 3].map((i) => (\n                  <Card key={i} className=\"space-y-2\">\n                    <CardHeader>\n                      <Skeleton className=\"h-4 w-[250px]\" />\n                      <Skeleton className=\"h-4 w-[200px]\" />\n                    </CardHeader>\n                    <CardFooter>\n                      <Skeleton className=\"h-4 w-[150px]\" />\n                    </CardFooter>\n                  </Card>\n                ))}\n              </div>\n            ) : savedQueries.length === 0 ? (\n              <Card className=\"p-8 text-center text-muted-foreground border-dashed\">\n                <Bookmark className=\"h-8 w-8 mx-auto mb-3 opacity-50\" />\n                <p>No saved queries yet</p>\n                <p className=\"text-xs mt-1\">Save a query from the editor toolbar</p>\n              </Card>\n            ) : (\n              <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5\">\n                {savedQueries.map((query, index) => (\n                  <motion.div\n                    key={query.id}\n                    initial={{ opacity: 0, y: 20 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    transition={{ delay: index * 0.05 }}\n                  >\n                    <Card\n                      className=\"hover:bg-accent/50 cursor-pointer transition-colors\"\n                      onClick={() => createTab(\"sql\", query.sql_text, query.name)}\n                    >\n                      <CardHeader>\n                        <CardTitle className=\"text-sm font-medium flex items-center space-x-2\">\n                          <Bookmark className=\"w-4 h-4 text-muted-foreground\" />\n                          <span className=\"truncate\">{query.name}</span>\n                        </CardTitle>\n                        <CardDescription className=\"text-xs font-mono text-muted-foreground truncate\">\n                          {query.sql_text.length > 50\n                            ? query.sql_text.slice(0, 50) + \"...\"\n                            : query.sql_text}\n                        </CardDescription>\n                      </CardHeader>\n                      <CardFooter className=\"text-xs text-muted-foreground\">\n                        {formatDistanceToNow(new Date(query.updated_at), { addSuffix: true })}\n                      </CardFooter>\n                    </Card>\n                  </motion.div>\n                ))}\n              </div>\n            )}\n          </TabsContent>\n\n          <TabsContent value=\"resources\" className=\"space-y-6\">\n            <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n              {resourceCards.map((card, index) => (\n                <motion.div\n                  key={index}\n                  initial={{ opacity: 0, y: 20 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  transition={{ delay: index * 0.05 }}\n                >\n                  <Card className=\"hover:bg-accent/50 transition-colors\">\n                    <CardHeader>\n                      <CardTitle className=\"text-sm font-medium flex items-center space-x-2\">\n                        <div className=\"p-2 rounded-full bg-primary/10\">\n                          <card.Icon className=\"w-4 h-4 text-primary\" />\n                        </div>\n                        <span className=\"text-muted-foreground\">{card.title}</span>\n                      </CardTitle>\n                      <CardDescription className=\"text-xs text-muted-foreground\">\n                        {card.description}\n                      </CardDescription>\n                    </CardHeader>\n                    <CardFooter>\n                      <a href={card.link} target=\"_blank\" rel=\"noopener noreferrer\">\n                        <Button variant=\"ghost\" className=\"w-full justify-start\">\n                          {card.action}\n                        </Button>\n                      </a>\n                    </CardFooter>\n                  </Card>\n                </motion.div>\n              ))}\n            </div>\n          </TabsContent>\n\n          <TabsContent value=\"iberodata\" className=\"space-y-6\">\n            <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6\">\n              {caioRicciutiProducts.map((product, index) => (\n                <motion.div\n                  key={index}\n                  initial={{ opacity: 0, y: 20 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  transition={{ delay: index * 0.05 }}\n                >\n                  <Card className=\"hover:bg-accent/50 transition-colors h-full\">\n                    <CardHeader>\n                      <CardTitle className=\"text-sm font-medium flex items-center space-x-2\">\n                        <div className=\"p-2 rounded-full bg-primary/10\">\n                          <product.Icon className=\"w-4 h-4 text-primary\" />\n                        </div>\n                        <span className=\"text-muted-foreground\">{product.title}</span>\n                      </CardTitle>\n                      <CardDescription className=\"text-xs text-muted-foreground\">\n                        {product.description}\n                      </CardDescription>\n                    </CardHeader>\n                    <CardFooter>\n                      <a href={product.link} target=\"_blank\" rel=\"noopener noreferrer\">\n                        <Button variant=\"ghost\" className=\"w-full justify-start\">\n                          Visit\n                        </Button>\n                      </a>\n                    </CardFooter>\n                  </Card>\n                </motion.div>\n              ))}\n            </div>\n          </TabsContent>\n        </Tabs>\n\n        <p className=\"text-muted-foreground text-center text-xs\">\n          Duck-UI Version: {duck_ui_version} - Released on: {duck_ui_release_date}\n        </p>\n      </div>\n    </div>\n  );\n};\n\nexport default HomeTab;\n"
  },
  {
    "path": "src/components/workspace/QueryHistory.tsx",
    "content": "import React, { useState } from \"react\";\nimport { useDuckStore, QueryHistoryItem } from \"@/store\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger,\n  SheetFooter,\n} from \"@/components/ui/sheet\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { format } from \"date-fns\";\nimport {\n  Copy,\n  CopyCheck,\n  FileClock,\n  Trash2,\n  AlertCircle,\n  Clock,\n  CheckCircle2,\n  XCircle,\n} from \"lucide-react\";\nimport { toast } from \"sonner\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\n\ninterface QueryHistoryProps {\n  isExpanded: boolean;\n  mode?: \"sheet\" | \"inline\";\n}\n\nconst QueryHistory: React.FC<QueryHistoryProps> = ({ isExpanded, mode = \"sheet\" }) => {\n  const queryHistory = useDuckStore((state) => state.queryHistory);\n  const clearHistory = useDuckStore((state) => state.clearHistory);\n  const [copiedQuery, setCopiedQuery] = useState<string | null>(null);\n  const [isOpen, setIsOpen] = useState(false);\n\n  const handleCopyQuery = (query: string) => {\n    navigator.clipboard.writeText(query);\n    setCopiedQuery(query);\n    toast.success(\"Query copied to clipboard\", {\n      duration: 1500,\n    });\n    setTimeout(() => setCopiedQuery(null), 1000);\n  };\n\n  const getStatusIcon = (item: QueryHistoryItem) => {\n    if (item.error) return <XCircle className=\"w-4 h-4 text-red-500\" />;\n    return <CheckCircle2 className=\"w-4 h-4 text-green-500\" />;\n  };\n\n  const historyContent = (\n    <>\n      {queryHistory.length === 0 ? (\n        <Alert>\n          <AlertCircle className=\"w-4 h-4 mt-6\" />\n          <AlertTitle>No History</AlertTitle>\n          <AlertDescription>\n            Your query history will appear here once you start executing queries.\n          </AlertDescription>\n        </Alert>\n      ) : (\n        <div className=\"space-y-3 mt-4\">\n          {queryHistory.map((item: QueryHistoryItem) => (\n            <Card key={item.id} className=\"relative\">\n              <CardContent className=\"p-3\">\n                <div className=\"flex items-start justify-between gap-2\">\n                  <div className=\"flex-1 space-y-2 min-w-0\">\n                    <div className=\"flex items-start gap-2 w-full\">\n                      <div className=\"mt-1 shrink-0\">{getStatusIcon(item)}</div>\n                      <pre className=\"font-mono text-xs bg-muted overflow-x-auto p-2 rounded flex-1 min-w-0\">\n                        <code className=\"break-all whitespace-pre-wrap\">\n                          {item.query.length > 150 ? item.query.slice(0, 150) + \"...\" : item.query}\n                        </code>\n                      </pre>\n                    </div>\n\n                    <div className=\"flex items-center gap-4 text-xs text-muted-foreground\">\n                      <div className=\"flex items-center gap-1\">\n                        <Clock className=\"w-3 h-3\" />\n                        {format(item.timestamp, \"MMM d, h:mm a\")}\n                      </div>\n                    </div>\n\n                    {item.error && (\n                      <Alert variant=\"destructive\" className=\"mt-2 py-2\">\n                        <AlertCircle className=\"w-3 h-3\" />\n                        <AlertDescription className=\"text-xs\">\n                          {item.error.length > 100 ? item.error.slice(0, 100) + \"...\" : item.error}\n                        </AlertDescription>\n                      </Alert>\n                    )}\n                  </div>\n\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"h-7 w-7 shrink-0\"\n                    onClick={() => handleCopyQuery(item.query)}\n                  >\n                    {copiedQuery === item.query ? (\n                      <CopyCheck className=\"w-3.5 h-3.5\" />\n                    ) : (\n                      <Copy className=\"w-3.5 h-3.5\" />\n                    )}\n                  </Button>\n                </div>\n              </CardContent>\n            </Card>\n          ))}\n        </div>\n      )}\n    </>\n  );\n\n  const clearHistoryButton = (\n    <>\n      {queryHistory.length > 0 && (\n        <AlertDialog>\n          <AlertDialogTrigger asChild>\n            <Button variant=\"destructive\" size=\"sm\">\n              <Trash2 className=\"w-4 h-4 mr-2\" />\n              Clear History\n            </Button>\n          </AlertDialogTrigger>\n          <AlertDialogContent>\n            <AlertDialogHeader>\n              <AlertDialogTitle>Clear Query History?</AlertDialogTitle>\n              <AlertDialogDescription>\n                This action cannot be undone. This will permanently delete your query history.\n              </AlertDialogDescription>\n            </AlertDialogHeader>\n            <AlertDialogFooter>\n              <AlertDialogCancel>Cancel</AlertDialogCancel>\n              <AlertDialogAction onClick={clearHistory}>Clear History</AlertDialogAction>\n            </AlertDialogFooter>\n          </AlertDialogContent>\n        </AlertDialog>\n      )}\n    </>\n  );\n\n  // Inline mode: render content directly without Sheet wrapper\n  if (mode === \"inline\") {\n    return (\n      <div className=\"flex flex-col h-full\">\n        <ScrollArea className=\"flex-1 px-3\">{historyContent}</ScrollArea>\n        <div className=\"p-3 border-t flex justify-end\">{clearHistoryButton}</div>\n      </div>\n    );\n  }\n\n  // Sheet mode: default behavior with Sheet wrapper\n  return (\n    <Sheet open={isOpen} onOpenChange={setIsOpen}>\n      {!isExpanded && (\n        <SheetTrigger asChild>\n          <Button variant=\"ghost\">\n            <FileClock className=\"w-8 h-8\" />\n          </Button>\n        </SheetTrigger>\n      )}\n      {isExpanded && (\n        <SheetTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            onClick={() => setIsOpen(true)}\n            className=\"flex items-center gap-2\"\n          >\n            <FileClock className=\"w-5 h-5\" />\n            Query History\n          </Button>\n        </SheetTrigger>\n      )}\n\n      <SheetContent side=\"right\" className=\"w-full sm:max-w-xl md:max-w-2xl\">\n        <SheetHeader>\n          <SheetTitle className=\"flex items-center gap-2\">\n            <FileClock className=\"w-5 h-5\" />\n            Query History\n          </SheetTitle>\n        </SheetHeader>\n\n        <ScrollArea className=\"h-[calc(100vh-12rem)] mt-4 pr-4\">{historyContent}</ScrollArea>\n\n        <SheetFooter className=\"absolute bottom-4 right-4\">{clearHistoryButton}</SheetFooter>\n      </SheetContent>\n    </Sheet>\n  );\n};\n\nexport default QueryHistory;\n"
  },
  {
    "path": "src/components/workspace/SettingsTab.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Settings, Trash2, UserPlus } from \"lucide-react\";\nimport { useTheme } from \"@/components/theme/theme-provider\";\nimport { useDuckStore } from \"@/store\";\nimport { getSetting, setSetting } from \"@/services/persistence/repositories/settingsRepository\";\nimport { DEFAULT_DUCKDB_MEMORY_LIMIT_MB } from \"@/store/slices/duckdbSlice\";\nimport { toast } from \"sonner\";\nimport ProfileEditor from \"@/components/profile/ProfileEditor\";\nimport ProfileAvatar from \"@/components/profile/ProfileAvatar\";\nimport PasswordDialog from \"@/components/profile/PasswordDialog\";\nimport type { Profile } from \"@/store/types\";\n\nexport default function SettingsTab() {\n  const { theme, setTheme } = useTheme();\n  const currentProfile = useDuckStore((s) => s.currentProfile);\n  const currentProfileId = useDuckStore((s) => s.currentProfileId);\n  const profiles = useDuckStore((s) => s.profiles);\n  const createProfile = useDuckStore((s) => s.createProfile);\n  const updateProfile = useDuckStore((s) => s.updateProfile);\n  const deleteProfile = useDuckStore((s) => s.deleteProfile);\n  const switchProfile = useDuckStore((s) => s.switchProfile);\n  const [isDeleting, setIsDeleting] = useState(false);\n  const [showCreateDialog, setShowCreateDialog] = useState(false);\n  const [switchTarget, setSwitchTarget] = useState<Profile | null>(null);\n  const [showPasswordDialog, setShowPasswordDialog] = useState(false);\n  const [pendingDeleteTargetId, setPendingDeleteTargetId] = useState<string | null>(null);\n  const [memoryLimitMb, setMemoryLimitMb] = useState<number>(DEFAULT_DUCKDB_MEMORY_LIMIT_MB);\n  const [isSavingPerformance, setIsSavingPerformance] = useState(false);\n\n  useEffect(() => {\n    if (!currentProfileId) return;\n    let cancelled = false;\n    (async () => {\n      try {\n        const raw = await getSetting(currentProfileId, \"duckdb\", \"memory_limit_mb\");\n        if (!cancelled && raw) {\n          const parsed = Number(JSON.parse(raw));\n          if (Number.isFinite(parsed)) {\n            setMemoryLimitMb(Math.floor(parsed));\n          }\n        }\n      } catch {\n        // ignore — fall back to default\n      }\n    })();\n    return () => {\n      cancelled = true;\n    };\n  }, [currentProfileId]);\n\n  const handleSaveMemoryLimit = async () => {\n    if (!currentProfileId) return;\n    const clamped = Math.max(256, Math.min(16384, Math.floor(memoryLimitMb || 0)));\n    if (clamped !== memoryLimitMb) setMemoryLimitMb(clamped);\n    setIsSavingPerformance(true);\n    try {\n      await setSetting(currentProfileId, \"duckdb\", \"memory_limit_mb\", JSON.stringify(clamped));\n      toast.success(\"Memory limit saved. Reload to apply.\");\n    } catch {\n      toast.error(\"Failed to save memory limit\");\n    } finally {\n      setIsSavingPerformance(false);\n    }\n  };\n\n  const handleProfileSave = async (values: { name: string; avatarEmoji: string }) => {\n    try {\n      await updateProfile({ name: values.name, avatarEmoji: values.avatarEmoji });\n      toast.success(\"Profile updated\");\n    } catch {\n      toast.error(\"Failed to update profile\");\n    }\n  };\n\n  const handleThemeChange = async (value: string) => {\n    setTheme(value as \"dark\" | \"light\" | \"system\");\n    if (currentProfileId) {\n      try {\n        await setSetting(currentProfileId, \"theme\", \"mode\", JSON.stringify(value));\n      } catch {\n        // Non-critical, theme still applied in-memory\n      }\n    }\n  };\n\n  const handleDeleteProfile = async () => {\n    if (!currentProfileId || profiles.length <= 1) return;\n    const otherProfile = profiles.find((p) => p.id !== currentProfileId);\n    if (!otherProfile) return;\n\n    if (otherProfile.hasPassword) {\n      setPendingDeleteTargetId(currentProfileId);\n      setSwitchTarget(otherProfile);\n      setShowPasswordDialog(true);\n    } else {\n      setIsDeleting(true);\n      try {\n        const deletingId = currentProfileId;\n        await switchProfile(otherProfile.id);\n        await deleteProfile(deletingId);\n        toast.success(\"Profile deleted\");\n      } catch {\n        toast.error(\"Failed to delete profile\");\n      } finally {\n        setIsDeleting(false);\n      }\n    }\n  };\n\n  const handleCreateProfile = async (values: {\n    name: string;\n    avatarEmoji: string;\n    password?: string;\n  }) => {\n    try {\n      await createProfile(values.name, values.password, values.avatarEmoji);\n      setShowCreateDialog(false);\n      toast.success(`Profile \"${values.name}\" created`);\n    } catch {\n      toast.error(\"Failed to create profile\");\n    }\n  };\n\n  const handleSwitchProfile = (profile: Profile) => {\n    if (profile.hasPassword) {\n      setSwitchTarget(profile);\n      setShowPasswordDialog(true);\n    } else {\n      switchProfile(profile.id);\n    }\n  };\n\n  const handlePasswordSubmit = async (password: string) => {\n    if (!switchTarget) return;\n    try {\n      if (pendingDeleteTargetId) {\n        setIsDeleting(true);\n        await switchProfile(switchTarget.id, password);\n        await deleteProfile(pendingDeleteTargetId);\n        setPendingDeleteTargetId(null);\n        toast.success(\"Profile deleted\");\n        setIsDeleting(false);\n      } else {\n        await switchProfile(switchTarget.id, password);\n      }\n      setShowPasswordDialog(false);\n    } catch {\n      toast.error(pendingDeleteTargetId ? \"Failed to delete profile\" : \"Incorrect password\");\n      setPendingDeleteTargetId(null);\n      setIsDeleting(false);\n    }\n  };\n\n  const otherProfiles = profiles.filter((p) => p.id !== currentProfileId);\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <div className=\"flex items-center gap-2 px-6 py-3 border-b\">\n        <Settings className=\"h-5 w-5\" />\n        <h2 className=\"text-lg font-semibold\">Settings</h2>\n      </div>\n\n      <ScrollArea className=\"flex-1\">\n        <div className=\"max-w-2xl mx-auto p-6\">\n          <Tabs defaultValue=\"profile\">\n            <TabsList className=\"mb-6\">\n              <TabsTrigger value=\"profile\">Profile</TabsTrigger>\n              <TabsTrigger value=\"general\">General</TabsTrigger>\n              <TabsTrigger value=\"performance\">Performance</TabsTrigger>\n            </TabsList>\n\n            <TabsContent value=\"profile\" className=\"mt-0 space-y-6\">\n              {/* Edit Current Profile */}\n              {currentProfile && (\n                <ProfileEditor\n                  mode=\"edit\"\n                  initialValues={{\n                    name: currentProfile.name,\n                    avatarEmoji: currentProfile.avatarEmoji,\n                  }}\n                  onSave={handleProfileSave}\n                  onCancel={() => {}}\n                />\n              )}\n\n              <Separator />\n\n              {/* Profiles Section */}\n              <div className=\"space-y-4\">\n                <div className=\"flex items-center justify-between\">\n                  <h3 className=\"text-sm font-medium\">Profiles</h3>\n                  <Button variant=\"outline\" size=\"sm\" onClick={() => setShowCreateDialog(true)}>\n                    <UserPlus className=\"h-4 w-4 mr-2\" />\n                    Create New Profile\n                  </Button>\n                </div>\n\n                <div className=\"space-y-2\">\n                  {otherProfiles.map((profile) => (\n                    <div\n                      key={profile.id}\n                      className=\"flex items-center gap-3 p-3 rounded-md border hover:bg-accent/50 cursor-pointer transition-colors\"\n                      onClick={() => handleSwitchProfile(profile)}\n                    >\n                      <ProfileAvatar avatarEmoji={profile.avatarEmoji} size=\"md\" />\n                      <div className=\"flex-1 min-w-0\">\n                        <span className=\"font-medium text-sm\">{profile.name}</span>\n                      </div>\n                      <span className=\"text-xs text-muted-foreground\">Switch</span>\n                    </div>\n                  ))}\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* Danger Zone */}\n              <div className=\"space-y-3\">\n                <h3 className=\"text-sm font-medium text-destructive\">Danger Zone</h3>\n                <AlertDialog>\n                  <AlertDialogTrigger asChild>\n                    <Button\n                      variant=\"destructive\"\n                      size=\"sm\"\n                      disabled={profiles.length <= 1 || isDeleting}\n                    >\n                      <Trash2 className=\"h-4 w-4 mr-2\" />\n                      Delete Profile\n                    </Button>\n                  </AlertDialogTrigger>\n                  <AlertDialogContent>\n                    <AlertDialogHeader>\n                      <AlertDialogTitle>Delete Profile</AlertDialogTitle>\n                      <AlertDialogDescription>\n                        This will permanently delete &quot;{currentProfile?.name}&quot; and all\n                        associated data including saved connections, query history, and AI\n                        configurations. This action cannot be undone.\n                      </AlertDialogDescription>\n                    </AlertDialogHeader>\n                    <AlertDialogFooter>\n                      <AlertDialogCancel>Cancel</AlertDialogCancel>\n                      <AlertDialogAction onClick={handleDeleteProfile}>Delete</AlertDialogAction>\n                    </AlertDialogFooter>\n                  </AlertDialogContent>\n                </AlertDialog>\n                {profiles.length <= 1 && (\n                  <p className=\"text-xs text-muted-foreground\">\n                    Cannot delete the only profile. Create another profile first.\n                  </p>\n                )}\n              </div>\n            </TabsContent>\n\n            <TabsContent value=\"performance\" className=\"mt-0 space-y-6\">\n              <div className=\"space-y-3\">\n                <Label htmlFor=\"memory-limit\" className=\"text-sm font-medium\">\n                  DuckDB memory limit (MB)\n                </Label>\n                <div className=\"flex items-center gap-2\">\n                  <Input\n                    id=\"memory-limit\"\n                    type=\"number\"\n                    min={256}\n                    max={16384}\n                    step={256}\n                    value={memoryLimitMb}\n                    onChange={(e) => setMemoryLimitMb(Number(e.target.value))}\n                    className=\"max-w-[160px]\"\n                  />\n                  <Button\n                    size=\"sm\"\n                    onClick={handleSaveMemoryLimit}\n                    disabled={isSavingPerformance || !currentProfileId}\n                  >\n                    Save\n                  </Button>\n                </div>\n                <p className=\"text-xs text-muted-foreground\">\n                  Caps DuckDB heap usage. Raise this if large sorts or aggregations fail with\n                  out-of-memory errors; lower it on memory-constrained devices. Browser WASM\n                  practical ceiling is around 4 GB. Changes take effect after reload.\n                </p>\n              </div>\n            </TabsContent>\n\n            <TabsContent value=\"general\" className=\"mt-0 space-y-6\">\n              <div className=\"space-y-3\">\n                <Label className=\"text-sm font-medium\">Theme</Label>\n                <RadioGroup value={theme} onValueChange={handleThemeChange}>\n                  <div className=\"flex items-center space-x-2\">\n                    <RadioGroupItem value=\"dark\" id=\"theme-dark\" />\n                    <Label htmlFor=\"theme-dark\">Dark</Label>\n                  </div>\n                  <div className=\"flex items-center space-x-2\">\n                    <RadioGroupItem value=\"light\" id=\"theme-light\" />\n                    <Label htmlFor=\"theme-light\">Light</Label>\n                  </div>\n                  <div className=\"flex items-center space-x-2\">\n                    <RadioGroupItem value=\"system\" id=\"theme-system\" />\n                    <Label htmlFor=\"theme-system\">System</Label>\n                  </div>\n                </RadioGroup>\n              </div>\n            </TabsContent>\n          </Tabs>\n        </div>\n      </ScrollArea>\n\n      {/* Create Profile Dialog */}\n      <Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>\n        <DialogContent className=\"sm:max-w-md\">\n          <DialogHeader>\n            <DialogTitle>Create Profile</DialogTitle>\n            <DialogDescription className=\"sr-only\">Create a new user profile</DialogDescription>\n          </DialogHeader>\n          <ProfileEditor\n            mode=\"create\"\n            onSave={handleCreateProfile}\n            onCancel={() => setShowCreateDialog(false)}\n          />\n        </DialogContent>\n      </Dialog>\n\n      {switchTarget && (\n        <PasswordDialog\n          open={showPasswordDialog}\n          onOpenChange={setShowPasswordDialog}\n          profile={switchTarget}\n          onSubmit={handlePasswordSubmit}\n        />\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/workspace/SortableTab.tsx",
    "content": "import React from \"react\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { useSortable } from \"@dnd-kit/sortable\";\nimport { TabsTrigger } from \"@/components/ui/tabs\";\nimport { X, Home, Terminal, GripVertical, Brain, Cable, Settings, BookOpen } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { useDuckStore, type EditorTabType } from \"@/store\";\n\ninterface Tab {\n  id: string;\n  title: string;\n  type: EditorTabType;\n  content: string | { database?: string; table?: string };\n}\n\ninterface SortableTabProps {\n  tab: Tab;\n  isActive: boolean;\n}\n\nconst SortableTab = React.memo(function SortableTab({ tab, isActive }: SortableTabProps) {\n  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n    id: tab.id,\n    disabled: tab.id === \"home\",\n  });\n\n  const isExecuting = useDuckStore((s) => !!s.executingTabs[tab.id]);\n  const closeTab = useDuckStore((s) => s.closeTab);\n\n  const style = {\n    transform: CSS.Transform.toString(transform),\n    transition,\n    width: tab.type === \"home\" ? \"100px\" : \"150px\",\n  };\n\n  // Closable tabs (everything except home)\n  const isClosable = tab.type !== \"home\";\n\n  const handleMiddleClick = (e: React.MouseEvent) => {\n    if (e.button === 1 && tab.id !== \"home\") {\n      e.preventDefault();\n      closeTab(tab.id);\n    }\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Delete\" && tab.id !== \"home\") {\n      closeTab(tab.id);\n    }\n  };\n\n  return (\n    <div\n      ref={setNodeRef}\n      style={style}\n      className={cn(\n        \"flex items-center group relative\",\n        isDragging ? \"opacity-50\" : \"\",\n        isActive ? \"z-10\" : \"z-0\"\n      )}\n      onAuxClick={handleMiddleClick}\n      onKeyDown={handleKeyDown}\n    >\n      {isClosable && (\n        <div\n          className=\"absolute left-0 top-0 h-full opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-grab active:cursor-grabbing z-50\"\n          {...attributes}\n          {...listeners}\n        >\n          <GripVertical className=\"h-4 w-4 mt-2 ml-1 text-secondary\" />\n        </div>\n      )}\n      <TabsTrigger\n        disabled={isExecuting}\n        value={tab.id}\n        className={cn(\n          \"flex h-8 items-center rounded-sm px-3 relative w-full\",\n          \"data-[state=active]:bg-primary data-[state=active]:text-black\",\n          \"transition-colors duration-200\",\n          \"hover:bg-primary/40\",\n          tab.id === \"home\" ? \"cursor-default\" : \"cursor-pointer\",\n          isClosable ? \"pl-7\" : \"pl-3\", // Add padding for drag handle\n          isExecuting ? \"pointer-events-none opacity-50\" : \"\"\n        )}\n      >\n        <div className=\"flex items-center space-x-2 overflow-hidden w-full\">\n          <div className=\"flex-shrink-0\">\n            {tab.type === \"home\" ? (\n              <Home className=\"h-4 w-4\" />\n            ) : tab.type === \"sql\" ? (\n              <Terminal className=\"h-4 w-4\" />\n            ) : tab.type === \"notebook\" ? (\n              <BookOpen className=\"h-4 w-4\" />\n            ) : tab.type === \"brain\" ? (\n              <Brain className=\"h-4 w-4\" />\n            ) : tab.type === \"connections\" ? (\n              <Cable className=\"h-4 w-4\" />\n            ) : tab.type === \"settings\" ? (\n              <Settings className=\"h-4 w-4\" />\n            ) : null}\n          </div>\n          <span className=\"truncate text-xs\">{tab.title}</span>\n          {isClosable && (\n            <div className=\"ml-auto flex items-center space-x-1 text-xs text-gray-500\">\n              <span\n                className=\"cursor-pointer hover:bg-red-500/10 p-1 rounded transition-colors\"\n                onClick={(e) => {\n                  e.stopPropagation();\n                  closeTab(tab.id);\n                }}\n              >\n                <X className=\"h-4 w-4 text-red-500 hover:text-red-500 transition-colors\" />\n              </span>\n            </div>\n          )}\n        </div>\n      </TabsTrigger>\n    </div>\n  );\n});\n\nexport default SortableTab;\n"
  },
  {
    "path": "src/components/workspace/SqlTab.tsx",
    "content": "// src/components/workspace/SqlTab.tsx\nimport React from \"react\";\nimport { useDuckStore } from \"@/store\";\nimport SqlEditor from \"@/components/editor/SqlEditor\";\nimport { ResizablePanel, ResizablePanelGroup, ResizableHandle } from \"@/components/ui/resizable\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport DuckUiTable from \"@/components/table/DuckUItable\";\nimport ChartVisualizationPro from \"@/components/charts/ChartVisualizationPro\";\nimport DuckBrainPanel from \"@/components/duck-brain/DuckBrainPanel\";\nimport { FileX2, Table, BarChart3, AlertTriangle } from \"lucide-react\";\nimport { Alert, AlertTitle, AlertDescription } from \"@/components/ui/alert\";\nimport { Skeleton } from \"../ui/skeleton\";\nimport { ErrorBoundary, FallbackProps } from \"react-error-boundary\";\n\nconst TableErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (\n  <div className=\"h-full flex items-center justify-center p-4\">\n    <div className=\"text-center max-w-md\">\n      <AlertTriangle className=\"mx-auto mb-4 text-destructive\" size={32} />\n      <h3 className=\"text-sm font-medium mb-2\">Failed to render table</h3>\n      <p className=\"text-xs text-muted-foreground mb-4\">\n        {error instanceof Error ? error.message : \"An error occurred while displaying the results.\"}\n      </p>\n      <button\n        onClick={resetErrorBoundary}\n        className=\"text-xs px-3 py-1.5 bg-primary text-primary-foreground rounded hover:bg-primary/90\"\n      >\n        Try again\n      </button>\n    </div>\n  </div>\n);\n\ninterface SqlTabProps {\n  tabId: string;\n}\n\nconst SqlTab: React.FC<SqlTabProps> = ({ tabId }) => {\n  const tabs = useDuckStore((s) => s.tabs);\n  const isExecuting = useDuckStore((s) => !!s.executingTabs[tabId]);\n  const updateTabChartConfig = useDuckStore((s) => s.updateTabChartConfig);\n  const isPanelOpen = useDuckStore((s) => s.duckBrain.isPanelOpen);\n  const currentTab = tabs.find((tab) => tab.id === tabId);\n\n  const renderResults = () => {\n    if (!currentTab || currentTab.type !== \"sql\") {\n      return null;\n    }\n\n    // Show skeleton loader while executing query\n\n    if (isExecuting) {\n      return (\n        <div className=\"h-full p-4\">\n          {/* Table Skeleton */}\n          <div className=\"space-y-4\">\n            {/* Skeleton Header */}\n            <div className=\"flex space-x-4\">\n              {Array.from({ length: 15 }).map((_, index) => (\n                <Skeleton key={`header-${index}`} className=\"h-4 w-32\" />\n              ))}\n            </div>\n\n            {/* Skeleton Rows */}\n            <div className=\"space-y-2\">\n              {Array.from({ length: 22 }).map((_, rowIndex) => (\n                <Skeleton key={`row-${rowIndex}`} className=\"flex space-x-4\">\n                  {Array.from({ length: 5 }).map((_, colIndex) => (\n                    <div\n                      key={`cell-${rowIndex}-${colIndex}`}\n                      className=\"h-5 w-24 rounded-md animate-pulse\"\n                    />\n                  ))}\n                </Skeleton>\n              ))}\n            </div>\n          </div>\n        </div>\n      );\n    }\n\n    // Show empty state if no query has been run\n    if (!currentTab.result) {\n      return (\n        <div className=\"h-full flex items-center justify-center\">\n          <div className=\"flex flex-col items-center\">\n            <FileX2 size={48} className=\"text-muted-foreground mb-4\" />\n            <p className=\"text-sm text-muted-foreground\">\n              There's no data yet! Run a query to get started.\n            </p>\n          </div>\n        </div>\n      );\n    }\n\n    // Show error if query failed\n    if (currentTab.result.error) {\n      return (\n        <div className=\"m-4\">\n          <Alert variant=\"destructive\">\n            <AlertTitle>Query Error</AlertTitle>\n            <AlertDescription>{currentTab.result.error}</AlertDescription>\n          </Alert>\n        </div>\n      );\n    }\n\n    // Show results in tabs (Table and Charts)\n    return (\n      <Tabs defaultValue=\"table\" className=\"h-full flex flex-col\">\n        <TabsList className=\"mx-4 mt-2\">\n          <TabsTrigger value=\"table\" className=\"flex items-center gap-2\">\n            <Table className=\"w-4 h-4\" />\n            Table\n          </TabsTrigger>\n          <TabsTrigger value=\"charts\" className=\"flex items-center gap-2\">\n            <BarChart3 className=\"w-4 h-4\" />\n            Charts\n          </TabsTrigger>\n        </TabsList>\n        <TabsContent value=\"table\" className=\"flex-1 min-h-0\">\n          <div className=\"h-full\">\n            <ErrorBoundary FallbackComponent={TableErrorFallback}>\n              <DuckUiTable data={currentTab.result.data} />\n            </ErrorBoundary>\n          </div>\n        </TabsContent>\n        <TabsContent value=\"charts\" className=\"flex-1 min-h-0\">\n          <div className=\"h-full\">\n            <ChartVisualizationPro\n              result={currentTab.result}\n              chartConfig={currentTab.chartConfig}\n              onConfigChange={(config) => updateTabChartConfig(tabId, config)}\n            />\n          </div>\n        </TabsContent>\n      </Tabs>\n    );\n  };\n\n  if (!currentTab || currentTab.type !== \"sql\") {\n    return null;\n  }\n\n  return (\n    <div className=\"h-full\">\n      <ResizablePanelGroup direction=\"horizontal\">\n        {/* Main Editor + Results Panel */}\n        <ResizablePanel defaultSize={isPanelOpen ? 70 : 100} minSize={50}>\n          <ResizablePanelGroup direction=\"vertical\">\n            <ResizablePanel defaultSize={50} minSize={25}>\n              <SqlEditor tabId={tabId} title={currentTab.title} />\n            </ResizablePanel>\n            <ResizableHandle withHandle />\n            <ResizablePanel defaultSize={50} minSize={25}>\n              {renderResults()}\n            </ResizablePanel>\n          </ResizablePanelGroup>\n        </ResizablePanel>\n\n        {/* Duck Brain Panel */}\n        {isPanelOpen && (\n          <>\n            <ResizableHandle withHandle />\n            <ResizablePanel defaultSize={30} minSize={20} maxSize={50}>\n              <DuckBrainPanel tabId={tabId} />\n            </ResizablePanel>\n          </>\n        )}\n      </ResizablePanelGroup>\n    </div>\n  );\n};\n\nexport default SqlTab;\n"
  },
  {
    "path": "src/components/workspace/WorkspaceTabs.tsx",
    "content": "// src/components/workspace/WorkspaceTabs.tsx\nimport { useMemo, useEffect, lazy, Suspense } from \"react\";\nimport { Tabs, TabsList, TabsContent } from \"@/components/ui/tabs\";\nimport { Button } from \"@/components/ui/button\";\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\nimport { Plus, XSquareIcon, Loader2, Terminal, BookOpen } from \"lucide-react\";\nimport { useQueryFromURL } from \"@/hooks/useQueryFromURL\";\nimport {\n  DndContext,\n  closestCenter,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors,\n  DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n  SortableContext,\n  sortableKeyboardCoordinates,\n  horizontalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuSeparator,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport SortableTab from \"@/components/workspace/SortableTab\";\nimport { useDuckStore } from \"@/store\";\nimport { ErrorBoundary, type FallbackProps } from \"react-error-boundary\";\nimport { AlertTriangle, RefreshCw } from \"lucide-react\";\n\nconst TabErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (\n  <div className=\"h-full flex items-center justify-center p-4\">\n    <div className=\"text-center max-w-md\">\n      <AlertTriangle className=\"mx-auto mb-4 text-destructive\" size={32} />\n      <h3 className=\"text-sm font-medium mb-2\">This tab encountered an error</h3>\n      <p className=\"text-xs text-muted-foreground mb-4\">\n        {error instanceof Error ? error.message : \"An unexpected error occurred.\"}\n      </p>\n      <button\n        onClick={resetErrorBoundary}\n        className=\"inline-flex items-center gap-2 text-xs px-3 py-1.5 bg-primary text-primary-foreground rounded hover:bg-primary/90\"\n      >\n        <RefreshCw size={14} />\n        Try again\n      </button>\n    </div>\n  </div>\n);\n\nconst HomeTab = lazy(() => import(\"@/components/workspace/HomeTab\"));\nconst SqlTab = lazy(() => import(\"@/components/workspace/SqlTab\"));\nconst NotebookTab = lazy(() => import(\"@/components/notebook/NotebookTab\"));\nconst BrainTab = lazy(() => import(\"@/components/workspace/BrainTab\"));\nconst ConnectionsTab = lazy(() => import(\"@/components/workspace/ConnectionsTab\"));\nconst SettingsTab = lazy(() => import(\"@/components/workspace/SettingsTab\"));\n\nconst TabFallback = () => (\n  <div className=\"h-full flex items-center justify-center\">\n    <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n  </div>\n);\n\nexport default function WorkspaceTabs() {\n  const tabs = useDuckStore((s) => s.tabs);\n  const activeTabId = useDuckStore((s) => s.activeTabId);\n  const createTab = useDuckStore((s) => s.createTab);\n  const setActiveTab = useDuckStore((s) => s.setActiveTab);\n  const moveTab = useDuckStore((s) => s.moveTab);\n  const closeAllTabs = useDuckStore((s) => s.closeAllTabs);\n  const initialize = useDuckStore((s) => s.initialize);\n  const isInitialized = useDuckStore((s) => s.isInitialized);\n\n  // Initialize DuckDB when component mounts\n  useEffect(() => {\n    if (!isInitialized) {\n      initialize().catch(console.error);\n    }\n  }, [isInitialized, initialize]);\n\n  // Handle loading query from URL parameters\n  useQueryFromURL();\n\n  const sensors = useSensors(\n    useSensor(PointerSensor),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    })\n  );\n\n  const handleDragEnd = (event: DragEndEvent) => {\n    const { active, over } = event;\n    if (active.id !== over?.id && active.id !== \"home\" && over?.id !== \"home\") {\n      const oldIndex = tabs.findIndex((tab) => tab.id === active.id);\n      const newIndex = tabs.findIndex((tab) => tab.id === over?.id);\n      moveTab(oldIndex, newIndex);\n    }\n  };\n\n  const sortedTabs = useMemo(() => {\n    const homeTab = tabs.find((tab) => tab.id === \"home\");\n    const otherTabs = tabs.filter((tab) => tab.id !== \"home\");\n    return homeTab ? [homeTab, ...otherTabs] : otherTabs;\n  }, [tabs]);\n\n  const addNewCodeTab = () => {\n    createTab(\"sql\", \"\");\n  };\n\n  const addNewNotebookTab = () => {\n    createTab(\"notebook\");\n  };\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <Tabs\n        value={activeTabId || undefined}\n        onValueChange={setActiveTab}\n        className=\"flex flex-col h-full\"\n      >\n        <div className=\"flex-shrink-0 flex items-center border-b bg-muted\">\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                className=\"rounded-none hover:bg-accent h-9 px-3 gap-2\"\n                aria-label=\"New tab\"\n              >\n                <Plus className=\"h-4 w-4\" />\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"start\">\n              <DropdownMenuItem onClick={addNewCodeTab}>\n                <Terminal className=\"h-4 w-4 mr-2\" />\n                SQL Tab\n              </DropdownMenuItem>\n              <DropdownMenuItem onClick={addNewNotebookTab}>\n                <BookOpen className=\"h-4 w-4 mr-2\" />\n                Notebook\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n          <ScrollArea className=\"flex-grow\">\n            <ContextMenu>\n              <ContextMenuTrigger>\n                <DndContext\n                  sensors={sensors}\n                  collisionDetection={closestCenter}\n                  onDragEnd={handleDragEnd}\n                >\n                  <SortableContext\n                    items={sortedTabs.map((tab) => tab.id)}\n                    strategy={horizontalListSortingStrategy}\n                  >\n                    <div className=\"flex\">\n                      <TabsList className=\"inline-flex h-9 space-x-1 items-center justify-start rounded-none w-full bg-transparent\">\n                        {sortedTabs.map((tab) => (\n                          <SortableTab key={tab.id} tab={tab} isActive={activeTabId === tab.id} />\n                        ))}\n                      </TabsList>\n                    </div>\n                  </SortableContext>\n                </DndContext>\n              </ContextMenuTrigger>\n              <ContextMenuContent>\n                <ContextMenuItem onClick={addNewCodeTab}>\n                  <Terminal className=\"h-4 w-4 mr-2\" />\n                  New SQL Tab\n                </ContextMenuItem>\n                <ContextMenuItem onClick={addNewNotebookTab}>\n                  <BookOpen className=\"h-4 w-4 mr-2\" />\n                  New Notebook\n                </ContextMenuItem>\n                <ContextMenuSeparator />\n                <ContextMenuItem onClick={closeAllTabs} className=\"text-red-600\">\n                  Close All Tabs <XSquareIcon className=\"ml-4 h-4 w-4\" />\n                </ContextMenuItem>\n              </ContextMenuContent>\n            </ContextMenu>\n            <ScrollBar orientation=\"horizontal\" />\n          </ScrollArea>\n        </div>\n        <div className=\"flex-1 overflow-hidden\">\n          {sortedTabs.map((tab) => (\n            <TabsContent\n              key={tab.id}\n              value={tab.id}\n              className=\"h-full m-0 outline-none data-[state=active]:flex-1\"\n            >\n              <ErrorBoundary FallbackComponent={TabErrorFallback}>\n                <Suspense fallback={<TabFallback />}>\n                  {tab.type === \"home\" ? (\n                    <HomeTab />\n                  ) : tab.type === \"sql\" ? (\n                    <SqlTab tabId={tab.id} />\n                  ) : tab.type === \"notebook\" ? (\n                    <NotebookTab tabId={tab.id} />\n                  ) : tab.type === \"brain\" ? (\n                    <BrainTab />\n                  ) : tab.type === \"connections\" ? (\n                    <ConnectionsTab />\n                  ) : tab.type === \"settings\" ? (\n                    <SettingsTab />\n                  ) : null}\n                </Suspense>\n              </ErrorBoundary>\n            </TabsContent>\n          ))}\n        </div>\n      </Tabs>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/hooks/useQueryFromURL.ts",
    "content": "import { useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useDuckStore } from \"@/store\";\nimport { toast } from \"sonner\";\n\n/**\n * Hook to handle loading queries from URL parameters.\n * Supports base64-encoded queries via ?query=<base64>&execute=true\n */\nexport function useQueryFromURL() {\n  const [searchParams, setSearchParams] = useSearchParams();\n  const { createTab, executeQuery, isInitialized } = useDuckStore();\n  const hasProcessedRef = useRef(false);\n\n  useEffect(() => {\n    // Only process once and after DuckDB is initialized\n    if (hasProcessedRef.current || !isInitialized) return;\n\n    const queryParam = searchParams.get(\"query\");\n    const executeParam = searchParams.get(\"execute\");\n\n    if (!queryParam) return;\n\n    try {\n      // Decode the base64 query\n      const decodedQuery = atob(queryParam);\n\n      if (!decodedQuery.trim()) {\n        toast.error(\"Empty query in URL\");\n        return;\n      }\n\n      // Create a new SQL tab with the decoded query\n      const tabId = createTab(\"sql\", decodedQuery);\n      hasProcessedRef.current = true;\n\n      toast.success(\"Query loaded from URL\");\n\n      // Auto-execute if requested\n      if (executeParam === \"true\" && tabId) {\n        // Small delay to ensure tab is created\n        setTimeout(() => {\n          executeQuery(decodedQuery, tabId);\n        }, 100);\n      }\n\n      // Clear the URL params after processing\n      setSearchParams({}, { replace: true });\n    } catch (error) {\n      console.error(\"Failed to decode query from URL:\", error);\n      toast.error(\"Failed to decode query from URL. Invalid base64 encoding.\");\n      hasProcessedRef.current = true;\n    }\n  }, [searchParams, setSearchParams, createTab, executeQuery, isInitialized]);\n}\n\n/**\n * Generate a shareable URL with the query encoded in base64\n */\nexport function generateQueryURL(query: string, autoExecute = false): string {\n  const base64Query = btoa(query);\n  const params = new URLSearchParams();\n  params.set(\"query\", base64Query);\n  if (autoExecute) {\n    params.set(\"execute\", \"true\");\n  }\n  return `${window.location.origin}${window.location.pathname}?${params.toString()}`;\n}\n\n/**\n * Copy the shareable query URL to clipboard\n */\nexport async function copyQueryURL(query: string, autoExecute = false): Promise<boolean> {\n  try {\n    const url = generateQueryURL(query, autoExecute);\n    await navigator.clipboard.writeText(url);\n    return true;\n  } catch (error) {\n    console.error(\"Failed to copy URL to clipboard:\", error);\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/index.css",
    "content": "@import \"tailwindcss\";\n@plugin \"@tailwindcss/typography\";\n\n:root {\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.2101 0.0318 264.6645);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.2101 0.0318 264.6645);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.2101 0.0318 264.6645);\n  --primary: oklch(0.6716 0.1368 48.513);\n  --primary-foreground: oklch(1 0 0);\n  --secondary: oklch(0.536 0.0398 196.028);\n  --secondary-foreground: oklch(1 0 0);\n  --muted: oklch(0.967 0.0029 264.5419);\n  --muted-foreground: oklch(0.551 0.0234 264.3637);\n  --accent: oklch(0.9491 0 0);\n  --accent-foreground: oklch(0.2101 0.0318 264.6645);\n  --destructive: oklch(0.6368 0.2078 25.3313);\n  --destructive-foreground: oklch(0.9851 0 0);\n  --border: oklch(0.9276 0.0058 264.5313);\n  --input: oklch(0.9276 0.0058 264.5313);\n  --ring: oklch(0.6716 0.1368 48.513);\n  --chart-1: oklch(0.594 0.0443 196.0233);\n  --chart-2: oklch(0.7214 0.1337 49.9802);\n  --chart-3: oklch(0.8721 0.0864 68.5474);\n  --chart-4: oklch(0.6268 0 0);\n  --chart-5: oklch(0.683 0 0);\n  --sidebar: oklch(0.967 0.0029 264.5419);\n  --sidebar-foreground: oklch(0.2101 0.0318 264.6645);\n  --sidebar-primary: oklch(0.6716 0.1368 48.513);\n  --sidebar-primary-foreground: oklch(1 0 0);\n  --sidebar-accent: oklch(1 0 0);\n  --sidebar-accent-foreground: oklch(0.2101 0.0318 264.6645);\n  --sidebar-border: oklch(0.9276 0.0058 264.5313);\n  --sidebar-ring: oklch(0.6716 0.1368 48.513);\n  --font-sans: Geist Mono, ui-monospace, monospace;\n  --font-serif: serif;\n  --font-mono: JetBrains Mono, monospace;\n  --radius: 0.075rem;\n  --shadow-x: 0px;\n  --shadow-y: 1px;\n  --shadow-blur: 4px;\n  --shadow-spread: 0px;\n  --shadow-opacity: 0.05;\n  --shadow-color: #000000;\n  --shadow-2xs: 0px 1px 4px 0px hsl(0 0% 0% / 0.03);\n  --shadow-xs: 0px 1px 4px 0px hsl(0 0% 0% / 0.03);\n  --shadow-sm: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 1px 2px -1px hsl(0 0% 0% / 0.05);\n  --shadow: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 1px 2px -1px hsl(0 0% 0% / 0.05);\n  --shadow-md: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 2px 4px -1px hsl(0 0% 0% / 0.05);\n  --shadow-lg: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 4px 6px -1px hsl(0 0% 0% / 0.05);\n  --shadow-xl: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 8px 10px -1px hsl(0 0% 0% / 0.05);\n  --shadow-2xl: 0px 1px 4px 0px hsl(0 0% 0% / 0.13);\n  --tracking-normal: 0rem;\n  --spacing: 0.25rem;\n}\n\n.dark {\n  --background: oklch(0.1797 0.0043 308.1928);\n  --foreground: oklch(0.8109 0 0);\n  --card: oklch(0.1822 0 0);\n  --card-foreground: oklch(0.8109 0 0);\n  --popover: oklch(0.1797 0.0043 308.1928);\n  --popover-foreground: oklch(0.8109 0 0);\n  --primary: oklch(0.852 0.199 91.936);\n  --primary-foreground: oklch(0.1797 0.0043 308.1928);\n  --secondary: oklch(0.594 0.0443 196.0233);\n  --secondary-foreground: oklch(0.1797 0.0043 308.1928);\n  --muted: oklch(0.252 0 0);\n  --muted-foreground: oklch(0.6268 0 0);\n  --accent: oklch(0.3211 0 0);\n  --accent-foreground: oklch(0.8109 0 0);\n  --destructive: oklch(0.594 0.0443 196.0233);\n  --destructive-foreground: oklch(0.1797 0.0043 308.1928);\n  --border: oklch(0.252 0 0);\n  --input: oklch(0.252 0 0);\n  --ring: oklch(0.681 0.162 75.834);\n  --chart-1: oklch(0.594 0.0443 196.0233);\n  --chart-2: oklch(0.7214 0.1337 49.9802);\n  --chart-3: oklch(0.8721 0.0864 68.5474);\n  --chart-4: oklch(0.6268 0 0);\n  --chart-5: oklch(0.683 0 0);\n  --sidebar: oklch(0.1822 0 0);\n  --sidebar-foreground: oklch(0.8109 0 0);\n  --sidebar-primary: oklch(0.7214 0.1337 49.9802);\n  --sidebar-primary-foreground: oklch(0.1797 0.0043 308.1928);\n  --sidebar-accent: oklch(0.3211 0 0);\n  --sidebar-accent-foreground: oklch(0.8109 0 0);\n  --sidebar-border: oklch(0.252 0 0);\n  --sidebar-ring: oklch(0.7214 0.1337 49.9802);\n  --font-sans: Geist Mono, ui-monospace, monospace;\n  --font-serif: serif;\n  --font-mono: JetBrains Mono, monospace;\n  --radius: 0.075rem;\n  --shadow-x: 0px;\n  --shadow-y: 1px;\n  --shadow-blur: 4px;\n  --shadow-spread: 0px;\n  --shadow-opacity: 0.05;\n  --shadow-color: #000000;\n  --shadow-2xs: 0px 1px 4px 0px hsl(0 0% 0% / 0.03);\n  --shadow-xs: 0px 1px 4px 0px hsl(0 0% 0% / 0.03);\n  --shadow-sm: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 1px 2px -1px hsl(0 0% 0% / 0.05);\n  --shadow: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 1px 2px -1px hsl(0 0% 0% / 0.05);\n  --shadow-md: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 2px 4px -1px hsl(0 0% 0% / 0.05);\n  --shadow-lg: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 4px 6px -1px hsl(0 0% 0% / 0.05);\n  --shadow-xl: 0px 1px 4px 0px hsl(0 0% 0% / 0.05), 0px 8px 10px -1px hsl(0 0% 0% / 0.05);\n  --shadow-2xl: 0px 1px 4px 0px hsl(0 0% 0% / 0.13);\n}\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n\n  --font-sans: var(--font-sans);\n  --font-mono: var(--font-mono);\n  --font-serif: var(--font-serif);\n\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n\n  --shadow-2xs: var(--shadow-2xs);\n  --shadow-xs: var(--shadow-xs);\n  --shadow-sm: var(--shadow-sm);\n  --shadow: var(--shadow);\n  --shadow-md: var(--shadow-md);\n  --shadow-lg: var(--shadow-lg);\n  --shadow-xl: var(--shadow-xl);\n  --shadow-2xl: var(--shadow-2xl);\n\n  --tracking-tighter: calc(var(--tracking-normal) - 0.05em);\n  --tracking-tight: calc(var(--tracking-normal) - 0.025em);\n  --tracking-normal: var(--tracking-normal);\n  --tracking-wide: calc(var(--tracking-normal) + 0.025em);\n  --tracking-wider: calc(var(--tracking-normal) + 0.05em);\n  --tracking-widest: calc(var(--tracking-normal) + 0.1em);\n}\n\nbody {\n  letter-spacing: var(--tracking-normal);\n}\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n/* uPlot tooltip */\n.uplot-tooltip {\n  background: rgba(255, 255, 255, 0.95);\n  border: 1px solid #e2e8f0;\n  border-radius: 8px;\n  padding: 8px 12px;\n  font-size: 12px;\n  line-height: 1.5;\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n  min-width: 120px;\n}\n.dark .uplot-tooltip {\n  background: rgba(30, 41, 59, 0.95);\n  border-color: #334155;\n  color: #e2e8f0;\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);\n}\n.uplot-tooltip-title {\n  font-weight: 600;\n  margin-bottom: 4px;\n  padding-bottom: 4px;\n  border-bottom: 1px solid #e2e8f0;\n}\n.dark .uplot-tooltip-title {\n  border-bottom-color: #334155;\n}\n.uplot-tooltip-row {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n.uplot-tooltip-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  flex-shrink: 0;\n}\n.uplot-tooltip-label {\n  flex: 1;\n  opacity: 0.7;\n}\n.uplot-tooltip-value {\n  font-weight: 600;\n  font-variant-numeric: tabular-nums;\n}\n.uplot-tooltip-pct {\n  opacity: 0.6;\n  font-weight: 400;\n}\n.uplot-tooltip-total {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  margin-top: 4px;\n  padding-top: 4px;\n  border-top: 1px solid #e2e8f0;\n  font-weight: 600;\n}\n.dark .uplot-tooltip-total {\n  border-top-color: #334155;\n}\n"
  },
  {
    "path": "src/lib/chartDataTransform.ts",
    "content": "/**\n * Advanced data transformation utilities for charting\n * Handles aggregations, grouping, sorting, and filtering\n */\n\nimport type { QueryResult } from \"@/store\";\nimport type { DataTransform, AggregationType, SeriesConfig } from \"@/store\";\n\nexport type TransformedData = Record<string, unknown>[];\n\n/**\n * Check if a column contains numeric values\n */\nexport const isNumericColumn = (data: Record<string, unknown>[], column: string): boolean => {\n  if (data.length === 0) return false;\n\n  // Sample first few non-null values\n  const sampleSize = Math.min(10, data.length);\n  for (let i = 0; i < sampleSize; i++) {\n    const value = data[i][column];\n    if (value !== null && value !== undefined) {\n      return typeof value === \"number\" || !isNaN(Number(value));\n    }\n  }\n\n  return false;\n};\n\n/**\n * Check if a column contains date/time values\n */\nexport const isDateColumn = (data: Record<string, unknown>[], column: string): boolean => {\n  if (data.length === 0) return false;\n\n  const value = data[0][column];\n  if (value instanceof Date) return true;\n  if (typeof value === \"string\") {\n    const date = new Date(value);\n    return !isNaN(date.getTime());\n  }\n\n  return false;\n};\n\n/**\n * Aggregate values based on aggregation type\n */\nexport const aggregate = (values: unknown[], aggregationType: AggregationType): number => {\n  const numericValues = values.map((v) => Number(v)).filter((v) => !isNaN(v));\n\n  if (numericValues.length === 0) return 0;\n\n  switch (aggregationType) {\n    case \"sum\":\n      return numericValues.reduce((a, b) => a + b, 0);\n    case \"avg\":\n      return numericValues.reduce((a, b) => a + b, 0) / numericValues.length;\n    case \"count\":\n      return values.length;\n    case \"min\":\n      return Math.min(...numericValues);\n    case \"max\":\n      return Math.max(...numericValues);\n    case \"none\":\n    default:\n      return numericValues[0] || 0;\n  }\n};\n\n/**\n * Group data by a column and aggregate values\n */\nexport const groupByColumn = (\n  data: Record<string, unknown>[],\n  groupByColumn: string,\n  valueColumn: string,\n  aggregationType: AggregationType\n): TransformedData => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const grouped = new Map<string | number, any[]>();\n\n  // Group values\n  data.forEach((row) => {\n    const key = row[groupByColumn] as string | number;\n    if (key === null || key === undefined) return;\n\n    if (!grouped.has(key)) {\n      grouped.set(key, []);\n    }\n    grouped.get(key)!.push(row[valueColumn]);\n  });\n\n  // Aggregate grouped values\n  const result: TransformedData = [];\n  grouped.forEach((values, key) => {\n    result.push({\n      [groupByColumn]: key,\n      [valueColumn]: aggregate(values, aggregationType),\n    });\n  });\n\n  return result;\n};\n\n/**\n * Sort data by column\n */\nexport const sortData = (\n  data: TransformedData,\n  sortBy: string,\n  sortOrder: \"asc\" | \"desc\"\n): TransformedData => {\n  return [...data].sort((a, b) => {\n    const aVal = a[sortBy];\n    const bVal = b[sortBy];\n\n    if (aVal === null || aVal === undefined) return 1;\n    if (bVal === null || bVal === undefined) return -1;\n\n    const comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;\n    return sortOrder === \"asc\" ? comparison : -comparison;\n  });\n};\n\n/**\n * Limit data to top/bottom N rows\n */\nexport const limitData = (data: TransformedData, limit: number): TransformedData => {\n  return data.slice(0, Math.max(1, limit));\n};\n\n/**\n * Transform query result data based on configuration\n */\nexport const transformData = (\n  result: QueryResult,\n  transform?: DataTransform,\n  xAxis?: string,\n  yAxis?: string | SeriesConfig[]\n): TransformedData => {\n  let data = [...result.data];\n\n  // Apply grouping and aggregation\n  if (transform?.groupBy && transform.groupBy !== xAxis) {\n    const valueColumn = typeof yAxis === \"string\" ? yAxis : yAxis?.[0]?.column;\n    if (valueColumn) {\n      const aggregation = transform.aggregation || \"sum\";\n      data = groupByColumn(data, transform.groupBy, valueColumn, aggregation);\n    }\n  }\n\n  // Apply sorting\n  if (transform?.sortBy && transform.sortOrder && transform.sortOrder !== \"none\") {\n    data = sortData(data, transform.sortBy, transform.sortOrder);\n  }\n\n  // Apply limit\n  if (transform?.limit && transform.limit > 0) {\n    data = limitData(data, transform.limit);\n  }\n\n  return data;\n};\n\n/**\n * Detect recommended chart types based on data characteristics\n * Only suggests chart types that are actually implemented in ChartVisualizationPro\n * Implemented types: bar, grouped_bar, stacked_bar, line, area, stacked_area, pie, donut, scatter\n */\nexport const suggestChartTypes = (\n  result: QueryResult,\n  xAxis?: string,\n  yAxis?: string | SeriesConfig[]\n): string[] => {\n  if (!xAxis || !yAxis) return [\"bar\"];\n\n  const suggestions: string[] = [];\n  const yColumn = typeof yAxis === \"string\" ? yAxis : yAxis[0]?.column;\n\n  if (!yColumn) return [\"bar\"];\n\n  const hasMultipleSeries = Array.isArray(yAxis) && yAxis.length > 1;\n  const xIsNumeric = isNumericColumn(result.data, xAxis);\n  const xIsDate = isDateColumn(result.data, xAxis);\n  const yIsNumeric = isNumericColumn(result.data, yColumn);\n  const dataSize = result.data.length;\n\n  // Time series data\n  if (xIsDate && yIsNumeric) {\n    suggestions.push(\"line\", \"area\", \"stacked_area\");\n  }\n\n  // Categorical x-axis with numeric y\n  if (!xIsNumeric && yIsNumeric) {\n    suggestions.push(\"bar\", \"stacked_bar\", \"grouped_bar\");\n    if (dataSize <= 10) {\n      suggestions.push(\"pie\", \"donut\");\n    }\n  }\n\n  // Numeric both axes\n  if (xIsNumeric && yIsNumeric) {\n    suggestions.push(\"scatter\", \"line\");\n  }\n\n  // Multi-series\n  if (hasMultipleSeries) {\n    suggestions.push(\"grouped_bar\", \"stacked_bar\");\n  }\n\n  return suggestions.length > 0 ? suggestions : [\"bar\"];\n};\n\n/**\n * Detect recommended aggregations for a column\n */\nexport const suggestAggregations = (result: QueryResult, column: string): AggregationType[] => {\n  if (isNumericColumn(result.data, column)) {\n    return [\"sum\", \"avg\", \"count\", \"min\", \"max\"];\n  }\n  return [\"count\"];\n};\n\n/**\n * Generate a color palette based on number of series\n */\nexport const generateColorPalette = (count: number): string[] => {\n  const baseColors = [\n    \"#D99B43\", // Gold\n    \"#8B5CF6\", // Purple\n    \"#3B82F6\", // Blue\n    \"#10B981\", // Green\n    \"#F59E0B\", // Amber\n    \"#EF4444\", // Red\n    \"#EC4899\", // Pink\n    \"#6366F1\", // Indigo\n    \"#14B8A6\", // Teal\n    \"#F97316\", // Orange\n    \"#A855F7\", // Violet\n    \"#06B6D4\", // Cyan\n  ];\n\n  if (count <= baseColors.length) {\n    return baseColors.slice(0, count);\n  }\n\n  // Generate more colors by interpolating\n  const colors = [...baseColors];\n  while (colors.length < count) {\n    const baseColor = baseColors[colors.length % baseColors.length];\n    colors.push(adjustColorBrightness(baseColor, (colors.length % 3) * 20 - 20));\n  }\n\n  return colors;\n};\n\n/**\n * Adjust color brightness\n */\nfunction adjustColorBrightness(hex: string, percent: number): string {\n  // Remove # if present\n  hex = hex.replace(\"#\", \"\");\n\n  // Parse RGB\n  const r = parseInt(hex.substring(0, 2), 16);\n  const g = parseInt(hex.substring(2, 4), 16);\n  const b = parseInt(hex.substring(4, 6), 16);\n\n  // Adjust brightness\n  const adjust = (value: number) => {\n    const adjusted = value + (value * percent) / 100;\n    return Math.max(0, Math.min(255, Math.round(adjusted)));\n  };\n\n  // Convert back to hex\n  const toHex = (value: number) => value.toString(16).padStart(2, \"0\");\n\n  return `#${toHex(adjust(r))}${toHex(adjust(g))}${toHex(adjust(b))}`;\n}\n\n/**\n * Format value for display in charts\n */\nexport const formatChartValue = (value: unknown, format?: string): string => {\n  if (value === null || value === undefined) return \"\";\n\n  if (typeof value === \"number\") {\n    if (format === \"percent\") {\n      return `${(value * 100).toFixed(2)}%`;\n    }\n    if (format === \"currency\") {\n      return new Intl.NumberFormat(\"en-US\", {\n        style: \"currency\",\n        currency: \"USD\",\n      }).format(value);\n    }\n    if (format === \"compact\") {\n      return new Intl.NumberFormat(\"en-US\", {\n        notation: \"compact\",\n        compactDisplay: \"short\",\n      }).format(value);\n    }\n    // Default number formatting\n    return new Intl.NumberFormat(\"en-US\", {\n      minimumFractionDigits: 0,\n      maximumFractionDigits: 2,\n    }).format(value);\n  }\n\n  if (value instanceof Date) {\n    return value.toLocaleDateString();\n  }\n\n  return String(value);\n};\n\n/**\n * Calculate statistical metrics for box plots\n */\nexport const calculateBoxPlotStats = (\n  data: number[]\n): {\n  min: number;\n  q1: number;\n  median: number;\n  q3: number;\n  max: number;\n  outliers: number[];\n} => {\n  const sorted = [...data].sort((a, b) => a - b);\n  const n = sorted.length;\n\n  const q1 = sorted[Math.floor(n * 0.25)];\n  const median = sorted[Math.floor(n * 0.5)];\n  const q3 = sorted[Math.floor(n * 0.75)];\n  const iqr = q3 - q1;\n\n  const lowerFence = q1 - 1.5 * iqr;\n  const upperFence = q3 + 1.5 * iqr;\n\n  const outliers = sorted.filter((v) => v < lowerFence || v > upperFence);\n  const filteredData = sorted.filter((v) => v >= lowerFence && v <= upperFence);\n\n  return {\n    min: filteredData[0] || sorted[0],\n    q1,\n    median,\n    q3,\n    max: filteredData[filteredData.length - 1] || sorted[n - 1],\n    outliers,\n  };\n};\n"
  },
  {
    "path": "src/lib/chartExport.ts",
    "content": "/**\n * Chart export utilities for PNG, SVG formats\n * Uses html2canvas and SVG manipulation for high-quality exports\n */\n\n/**\n * Helper to trigger a blob download\n */\nconst downloadBlob = (blob: Blob, fileName: string) => {\n  const url = URL.createObjectURL(blob);\n  const link = document.createElement(\"a\");\n  link.href = url;\n  link.download = fileName;\n  document.body.appendChild(link);\n  link.click();\n  document.body.removeChild(link);\n  URL.revokeObjectURL(url);\n};\n\n/**\n * Export chart as PNG image.\n * For canvas-based charts (uPlot), captures the canvas directly.\n * For SVG-based charts (pie/donut), falls back to html2canvas.\n */\nexport const exportChartAsPNG = async (\n  chartElement: HTMLElement,\n  fileName: string = \"chart.png\",\n  backgroundColor: string = \"#ffffff\"\n): Promise<void> => {\n  try {\n    // Direct canvas capture (uPlot renders to <canvas>)\n    const sourceCanvas = chartElement.querySelector(\"canvas\");\n    if (sourceCanvas && sourceCanvas.width > 0 && sourceCanvas.height > 0) {\n      const offscreen = document.createElement(\"canvas\");\n      offscreen.width = sourceCanvas.width;\n      offscreen.height = sourceCanvas.height;\n      const ctx = offscreen.getContext(\"2d\");\n      if (!ctx) throw new Error(\"Failed to get canvas context\");\n      ctx.fillStyle = backgroundColor;\n      ctx.fillRect(0, 0, offscreen.width, offscreen.height);\n      ctx.drawImage(sourceCanvas, 0, 0);\n\n      const blob = await new Promise<Blob>((resolve, reject) => {\n        offscreen.toBlob(\n          (b) => (b ? resolve(b) : reject(new Error(\"Failed to create blob\"))),\n          \"image/png\"\n        );\n      });\n      downloadBlob(blob, fileName);\n      return;\n    }\n\n    // Fallback: html2canvas for SVG-based charts (pie/donut)\n    const html2canvas = (await import(\"html2canvas\")).default;\n    const rendered = await html2canvas(chartElement, {\n      backgroundColor,\n      scale: 2,\n      logging: false,\n      useCORS: true,\n    });\n\n    rendered.toBlob((blob) => {\n      if (!blob) throw new Error(\"Failed to create image blob\");\n      downloadBlob(blob, fileName);\n    }, \"image/png\");\n  } catch (error) {\n    console.error(\"Failed to export chart as PNG:\", error);\n    throw new Error(\"Failed to export chart as PNG. Please try again.\");\n  }\n};\n\n/**\n * Export chart as SVG (works for SVG-based charts like pie/donut)\n * For canvas-based charts (uPlot), falls back to PNG export.\n */\nexport const exportChartAsSVG = async (\n  chartElement: HTMLElement,\n  fileName: string = \"chart.svg\"\n): Promise<void> => {\n  try {\n    // Find SVG element in the chart\n    const svgElement = chartElement.querySelector(\"svg\");\n    if (!svgElement) {\n      // Canvas-based chart (uPlot) — fall back to PNG\n      await exportChartAsPNG(chartElement, fileName.replace(/\\.svg$/, \".png\"));\n      return;\n    }\n\n    // Clone SVG to avoid modifying original\n    const clonedSvg = svgElement.cloneNode(true) as SVGElement;\n\n    // Add XML namespace if not present\n    if (!clonedSvg.hasAttribute(\"xmlns\")) {\n      clonedSvg.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n    }\n\n    // Get SVG string\n    const serializer = new XMLSerializer();\n    const svgString = serializer.serializeToString(clonedSvg);\n\n    // Create blob and download\n    const blob = new Blob([svgString], { type: \"image/svg+xml;charset=utf-8\" });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement(\"a\");\n    link.href = url;\n    link.download = fileName;\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n    URL.revokeObjectURL(url);\n  } catch (error) {\n    console.error(\"Failed to export chart as SVG:\", error);\n    throw new Error(\"Failed to export chart as SVG. Please try again.\");\n  }\n};\n\n/**\n * Copy chart as image to clipboard\n */\nexport const copyChartToClipboard = async (\n  chartElement: HTMLElement,\n  backgroundColor: string = \"#ffffff\"\n): Promise<void> => {\n  try {\n    let blob: Blob;\n\n    // Direct canvas capture (uPlot)\n    const sourceCanvas = chartElement.querySelector(\"canvas\");\n    if (sourceCanvas && sourceCanvas.width > 0 && sourceCanvas.height > 0) {\n      const offscreen = document.createElement(\"canvas\");\n      offscreen.width = sourceCanvas.width;\n      offscreen.height = sourceCanvas.height;\n      const ctx = offscreen.getContext(\"2d\");\n      if (!ctx) throw new Error(\"Failed to get canvas context\");\n      ctx.fillStyle = backgroundColor;\n      ctx.fillRect(0, 0, offscreen.width, offscreen.height);\n      ctx.drawImage(sourceCanvas, 0, 0);\n\n      blob = await new Promise<Blob>((resolve, reject) => {\n        offscreen.toBlob(\n          (b) => (b ? resolve(b) : reject(new Error(\"Failed to create blob\"))),\n          \"image/png\"\n        );\n      });\n    } else {\n      // Fallback: html2canvas for SVG-based charts\n      const html2canvas = (await import(\"html2canvas\")).default;\n      const canvas = await html2canvas(chartElement, {\n        backgroundColor,\n        scale: 2,\n        logging: false,\n        useCORS: true,\n      });\n\n      blob = await new Promise<Blob>((resolve, reject) => {\n        canvas.toBlob(\n          (b) => (b ? resolve(b) : reject(new Error(\"Failed to create blob\"))),\n          \"image/png\"\n        );\n      });\n    }\n\n    await navigator.clipboard.write([new ClipboardItem({ \"image/png\": blob })]);\n  } catch (error) {\n    console.error(\"Failed to copy chart to clipboard:\", error);\n    throw new Error(\"Failed to copy chart to clipboard. Please try again.\");\n  }\n};\n\n/**\n * Print chart\n */\nexport const printChart = (chartElement: HTMLElement): void => {\n  const printWindow = window.open(\"\", \"_blank\");\n  if (!printWindow) {\n    throw new Error(\"Failed to open print window. Please allow popups.\");\n  }\n\n  // Try SVG first, then canvas (uPlot)\n  const svgElement = chartElement.querySelector(\"svg\");\n  const canvasElement = chartElement.querySelector(\"canvas\");\n\n  if (!svgElement && !canvasElement) {\n    printWindow.close();\n    throw new Error(\"No chart found to print\");\n  }\n\n  let chartContent: string;\n  if (svgElement) {\n    const clonedSvg = svgElement.cloneNode(true) as SVGElement;\n    const serializer = new XMLSerializer();\n    chartContent = serializer.serializeToString(clonedSvg);\n  } else {\n    const dataUrl = canvasElement!.toDataURL(\"image/png\");\n    chartContent = `<img src=\"${dataUrl}\" style=\"max-width:100%;height:auto;\" />`;\n  }\n\n  printWindow.document.write(`\n    <!DOCTYPE html>\n    <html>\n      <head>\n        <title>Print Chart</title>\n        <style>\n          @media print {\n            body {\n              margin: 0;\n              padding: 20px;\n            }\n            svg, img {\n              max-width: 100%;\n              height: auto;\n            }\n          }\n        </style>\n      </head>\n      <body>\n        ${chartContent}\n        <script>\n          window.onload = function() {\n            window.print();\n            window.onafterprint = function() {\n              window.close();\n            };\n          };\n        </script>\n      </body>\n    </html>\n  `);\n\n  printWindow.document.close();\n};\n\n/**\n * Generate shareable URL with chart configuration\n */\nexport const generateShareableURL = (\n  chartConfig: Record<string, unknown>,\n  querySQL?: string\n): string => {\n  const params = new URLSearchParams();\n\n  // Encode chart configuration\n  params.set(\"chart\", btoa(JSON.stringify(chartConfig)));\n\n  // Optionally include query\n  if (querySQL) {\n    params.set(\"query\", btoa(querySQL));\n  }\n\n  return `${window.location.origin}${window.location.pathname}?${params.toString()}`;\n};\n\n/**\n * Parse chart configuration from URL\n */\nexport const parseChartFromURL = (): {\n  chartConfig?: Record<string, unknown>;\n  query?: string;\n} | null => {\n  try {\n    const params = new URLSearchParams(window.location.search);\n\n    const chartParam = params.get(\"chart\");\n    const queryParam = params.get(\"query\");\n\n    if (!chartParam) return null;\n\n    return {\n      chartConfig: JSON.parse(atob(chartParam)),\n      query: queryParam ? atob(queryParam) : undefined,\n    };\n  } catch (error) {\n    console.error(\"Failed to parse chart from URL:\", error);\n    return null;\n  }\n};\n\n/**\n * Get chart element dimensions for export\n */\nexport const getChartDimensions = (\n  chartElement: HTMLElement\n): { width: number; height: number } => {\n  const rect = chartElement.getBoundingClientRect();\n  return {\n    width: Math.round(rect.width),\n    height: Math.round(rect.height),\n  };\n};\n"
  },
  {
    "path": "src/lib/chartUtils.ts",
    "content": "/**\n * Format a number with thousand separators\n * @param value - The number to format\n * @returns Formatted string (e.g., 1,234,567)\n */\nexport const formatNumber = (value: number): string => {\n  if (typeof value !== \"number\" || isNaN(value)) return \"0\";\n\n  return new Intl.NumberFormat(\"en-US\", {\n    minimumFractionDigits: 0,\n    maximumFractionDigits: 2,\n  }).format(value);\n};\n\n/**\n * Format a number with suffix (K, M, B)\n * @param value - The number to format\n * @returns Formatted string with suffix (e.g., 1.23M)\n */\nexport const formatNumberWithSuffix = (value: number): string => {\n  if (typeof value !== \"number\" || isNaN(value)) return \"0\";\n\n  const absValue = Math.abs(value);\n  const sign = value < 0 ? \"-\" : \"\";\n\n  if (absValue >= 1e9) {\n    return `${sign}${(absValue / 1e9).toFixed(2)}B`;\n  } else if (absValue >= 1e6) {\n    return `${sign}${(absValue / 1e6).toFixed(2)}M`;\n  } else if (absValue >= 1e3) {\n    return `${sign}${(absValue / 1e3).toFixed(2)}K`;\n  }\n\n  return formatNumber(value);\n};\n\n/**\n * Format a number as percentage\n * @param value - The number to format (0-1 or 0-100)\n * @param isDecimal - Whether the value is in decimal form (0-1)\n * @returns Formatted percentage string (e.g., 45.67%)\n */\nexport const formatPercentage = (value: number, isDecimal: boolean = true): string => {\n  if (typeof value !== \"number\" || isNaN(value)) return \"0%\";\n\n  return new Intl.NumberFormat(\"en-US\", {\n    minimumFractionDigits: 0,\n    maximumFractionDigits: 2,\n    style: \"percent\",\n  }).format(isDecimal ? value : value / 100);\n};\n\n/**\n * Format a number as currency\n * @param value - The number to format\n * @param currency - Currency code (default: USD)\n * @returns Formatted currency string (e.g., $1,234.56)\n */\nexport const formatCurrency = (value: number, currency: string = \"USD\"): string => {\n  if (typeof value !== \"number\" || isNaN(value)) return \"$0.00\";\n\n  return new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency,\n    minimumFractionDigits: 2,\n    maximumFractionDigits: 2,\n  }).format(value);\n};\n\n/**\n * Shorten X-axis labels for better display\n * @param value - The label value\n * @param maxLength - Maximum length before truncation\n * @returns Shortened label\n */\nexport const shortenLabel = (value: string, maxLength: number = 15): string => {\n  if (typeof value !== \"string\") return String(value);\n\n  if (value.length <= maxLength) return value;\n\n  return `${value.substring(0, maxLength)}...`;\n};\n\n/**\n * Calculate optimal tick count based on data size\n * @param dataLength - Number of data points\n * @returns Optimal number of ticks\n */\nexport const calculateTickCount = (dataLength: number): number => {\n  if (dataLength <= 5) return dataLength;\n  if (dataLength <= 10) return 5;\n  if (dataLength <= 20) return 7;\n  return 10;\n};\n\n/**\n * Generate gradient definitions for charts\n * @param id - Unique gradient ID\n * @param color - Base color\n * @returns Gradient definition object\n */\nexport const createGradient = (id: string, color: string) => ({\n  id,\n  x1: \"0\",\n  y1: \"0\",\n  x2: \"0\",\n  y2: \"1\",\n  stops: [\n    { offset: \"5%\", stopColor: color, stopOpacity: 0.8 },\n    { offset: \"95%\", stopColor: color, stopOpacity: 0.1 },\n  ],\n});\n"
  },
  {
    "path": "src/lib/cloudStorage/index.ts",
    "content": "/**\n * Cloud Storage Service\n * Manages connections to S3, Google Cloud Storage, and Azure Blob Storage\n */\n\nimport { useDuckStore } from \"@/store\";\nimport { generateUUID } from \"@/lib/utils\";\nimport { sqlEscapeString } from \"@/lib/sqlSanitize\";\n\n// Cloud provider types\nexport type CloudProviderType = \"s3\" | \"gcs\" | \"azure\";\n\n// Cloud connection configuration\nexport interface CloudConnection {\n  id: string;\n  name: string;\n  type: CloudProviderType;\n\n  // S3 / S3-compatible (MinIO, R2, DigitalOcean Spaces)\n  bucket?: string;\n  region?: string;\n  accessKeyId?: string;\n  secretAccessKey?: string;\n  endpoint?: string; // For S3-compatible services\n\n  // Google Cloud Storage (uses HMAC keys for S3-compatible access)\n  projectId?: string;\n  hmacKeyId?: string;\n  hmacSecret?: string;\n\n  // Azure Blob Storage\n  accountName?: string;\n  accountKey?: string;\n  containerName?: string;\n\n  // Metadata\n  addedAt: Date;\n  isConnected: boolean;\n  lastError?: string;\n}\n\n// Cloud file entry\nexport interface CloudFile {\n  name: string;\n  path: string;\n  type: \"file\" | \"folder\";\n  size?: number;\n  lastModified?: Date;\n  extension?: string;\n}\n\n// HTTP/S3 support status\nexport interface CloudSupportStatus {\n  httpfsAvailable: boolean;\n  secretsSupported: boolean;\n  httpsSupported: boolean;\n  s3Supported: boolean;\n  error?: string;\n}\n\n// IndexedDB for storing cloud connections (credentials encrypted or in memory only)\nconst DB_NAME = \"duck-ui-cloud\";\nconst STORE_NAME = \"cloud-connections\";\nconst DB_VERSION = 1;\n\nasync function openDatabase(): Promise<IDBDatabase> {\n  return new Promise((resolve, reject) => {\n    const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve(request.result);\n\n    request.onupgradeneeded = (event) => {\n      const db = (event.target as IDBOpenDBRequest).result;\n      if (!db.objectStoreNames.contains(STORE_NAME)) {\n        db.createObjectStore(STORE_NAME, { keyPath: \"id\" });\n      }\n    };\n  });\n}\n\n/**\n * Cloud Storage Service - Singleton\n */\nclass CloudStorageService {\n  private db: IDBDatabase | null = null;\n  private connections: Map<string, CloudConnection> = new Map();\n  private initialized = false;\n  private supportStatus: CloudSupportStatus | null = null;\n\n  /**\n   * Initialize the service\n   */\n  async init(): Promise<void> {\n    if (this.initialized) return;\n\n    try {\n      this.db = await openDatabase();\n      await this.loadPersistedConnections();\n\n      // Check what cloud features are supported\n      this.supportStatus = await this.checkCloudSupport();\n\n      this.initialized = true;\n    } catch (error) {\n      console.error(\"Failed to initialize CloudStorageService:\", error);\n      throw error;\n    }\n  }\n\n  /**\n   * Check what cloud storage features are supported in this DuckDB-WASM instance\n   */\n  async checkCloudSupport(): Promise<CloudSupportStatus> {\n    const status: CloudSupportStatus = {\n      httpfsAvailable: false,\n      secretsSupported: false,\n      httpsSupported: false,\n      s3Supported: false,\n    };\n\n    const { connection } = useDuckStore.getState();\n    if (!connection) {\n      status.error = \"No DuckDB connection available\";\n      return status;\n    }\n\n    // Test 1: Try to install/load httpfs\n    try {\n      await connection.query(`INSTALL httpfs`);\n      await connection.query(`LOAD httpfs`);\n      status.httpfsAvailable = true;\n    } catch (e) {\n      console.log(\"httpfs not available:\", e);\n    }\n\n    // Test 2: Try to create a secret\n    if (status.httpfsAvailable) {\n      try {\n        await connection.query(`\n          CREATE OR REPLACE SECRET __cloud_test_secret (\n            TYPE s3,\n            KEY_ID 'test',\n            SECRET 'test',\n            REGION 'us-east-1'\n          )\n        `);\n        await connection.query(`DROP SECRET IF EXISTS __cloud_test_secret`);\n        status.secretsSupported = true;\n      } catch (e) {\n        console.log(\"Secrets not supported:\", e);\n      }\n    }\n\n    // Test 3: Check if read_parquet function is available\n    try {\n      const result = await connection.query(`\n        SELECT function_name FROM duckdb_functions()\n        WHERE function_name = 'read_parquet'\n        LIMIT 1\n      `);\n      status.httpsSupported = result.toArray().length > 0;\n    } catch {\n      status.httpsSupported = false;\n    }\n\n    // Test 4: S3 is supported if httpfs + secrets work\n    status.s3Supported = status.httpfsAvailable && status.secretsSupported;\n\n    return status;\n  }\n\n  /**\n   * Get current support status\n   */\n  getSupportStatus(): CloudSupportStatus | null {\n    return this.supportStatus;\n  }\n\n  /**\n   * Load connections from IndexedDB\n   */\n  private async loadPersistedConnections(): Promise<void> {\n    if (!this.db) return;\n\n    return new Promise((resolve, reject) => {\n      const transaction = this.db!.transaction(STORE_NAME, \"readonly\");\n      const store = transaction.objectStore(STORE_NAME);\n      const request = store.getAll();\n\n      request.onerror = () => reject(request.error);\n      request.onsuccess = () => {\n        const connections = request.result as CloudConnection[];\n        for (const conn of connections) {\n          conn.addedAt = new Date(conn.addedAt);\n          conn.isConnected = false; // Reset on load\n          this.connections.set(conn.id, conn);\n        }\n        resolve();\n      };\n    });\n  }\n\n  /**\n   * Save connection to IndexedDB\n   */\n  private async persistConnection(conn: CloudConnection): Promise<void> {\n    if (!this.db) return;\n\n    // Don't persist sensitive credentials - store metadata only\n    const safeConn = {\n      ...conn,\n      accessKeyId: undefined,\n      secretAccessKey: undefined,\n      hmacKeyId: undefined,\n      hmacSecret: undefined,\n      accountKey: undefined,\n    };\n\n    return new Promise((resolve, reject) => {\n      const transaction = this.db!.transaction(STORE_NAME, \"readwrite\");\n      const store = transaction.objectStore(STORE_NAME);\n      const request = store.put(safeConn);\n\n      request.onerror = () => reject(request.error);\n      request.onsuccess = () => resolve();\n    });\n  }\n\n  /**\n   * Remove connection from IndexedDB\n   */\n  private async removePersistedConnection(id: string): Promise<void> {\n    if (!this.db) return;\n\n    return new Promise((resolve, reject) => {\n      const transaction = this.db!.transaction(STORE_NAME, \"readwrite\");\n      const store = transaction.objectStore(STORE_NAME);\n      const request = store.delete(id);\n\n      request.onerror = () => reject(request.error);\n      request.onsuccess = () => resolve();\n    });\n  }\n\n  /**\n   * Add a new cloud connection\n   */\n  async addConnection(\n    config: Omit<CloudConnection, \"id\" | \"addedAt\" | \"isConnected\">\n  ): Promise<CloudConnection> {\n    const id = generateUUID();\n\n    const conn: CloudConnection = {\n      ...config,\n      id,\n      addedAt: new Date(),\n      isConnected: false,\n    };\n\n    this.connections.set(id, conn);\n    await this.persistConnection(conn);\n\n    return conn;\n  }\n\n  /**\n   * Update an existing connection\n   */\n  async updateConnection(\n    id: string,\n    updates: Partial<CloudConnection>\n  ): Promise<CloudConnection | null> {\n    const existing = this.connections.get(id);\n    if (!existing) return null;\n\n    const updated = { ...existing, ...updates };\n    this.connections.set(id, updated);\n    await this.persistConnection(updated);\n\n    return updated;\n  }\n\n  /**\n   * Remove a cloud connection\n   */\n  async removeConnection(id: string): Promise<void> {\n    // Disconnect first if connected\n    const conn = this.connections.get(id);\n    if (conn?.isConnected) {\n      await this.disconnect(id);\n    }\n\n    this.connections.delete(id);\n    await this.removePersistedConnection(id);\n  }\n\n  /**\n   * Get all connections\n   */\n  getConnections(): CloudConnection[] {\n    return Array.from(this.connections.values());\n  }\n\n  /**\n   * Get a specific connection\n   */\n  getConnection(id: string): CloudConnection | undefined {\n    return this.connections.get(id);\n  }\n\n  /**\n   * Setup DuckDB secret for a connection\n   */\n  async connect(id: string): Promise<boolean> {\n    const conn = this.connections.get(id);\n    if (!conn) {\n      throw new Error(`Connection not found: ${id}`);\n    }\n\n    if (!this.supportStatus?.secretsSupported) {\n      throw new Error(\n        \"Cloud storage secrets are not supported in this browser. \" +\n          \"DuckDB-WASM has limited cloud storage support due to CORS restrictions.\"\n      );\n    }\n\n    const { connection: duckConn } = useDuckStore.getState();\n    if (!duckConn) {\n      throw new Error(\"No DuckDB connection available\");\n    }\n\n    try {\n      const secretName = `cloud_${conn.id.replace(/-/g, \"_\")}`;\n\n      switch (conn.type) {\n        case \"s3\":\n          await duckConn.query(`\n            CREATE OR REPLACE SECRET ${secretName} (\n              TYPE s3,\n              KEY_ID '${sqlEscapeString(conn.accessKeyId || \"\")}',\n              SECRET '${sqlEscapeString(conn.secretAccessKey || \"\")}',\n              REGION '${sqlEscapeString(conn.region || \"us-east-1\")}'\n              ${conn.endpoint ? `, ENDPOINT '${sqlEscapeString(conn.endpoint)}'` : \"\"}\n            )\n          `);\n          break;\n\n        case \"gcs\":\n          // GCS uses S3-compatible HMAC keys\n          await duckConn.query(`\n            CREATE OR REPLACE SECRET ${secretName} (\n              TYPE gcs,\n              KEY_ID '${sqlEscapeString(conn.hmacKeyId || \"\")}',\n              SECRET '${sqlEscapeString(conn.hmacSecret || \"\")}'\n            )\n          `);\n          break;\n\n        case \"azure\":\n          await duckConn.query(`\n            CREATE OR REPLACE SECRET ${secretName} (\n              TYPE azure,\n              ACCOUNT_NAME '${sqlEscapeString(conn.accountName || \"\")}',\n              ACCOUNT_KEY '${sqlEscapeString(conn.accountKey || \"\")}'\n            )\n          `);\n          break;\n      }\n\n      conn.isConnected = true;\n      conn.lastError = undefined;\n      return true;\n    } catch (error) {\n      conn.isConnected = false;\n      conn.lastError = error instanceof Error ? error.message : String(error);\n      throw error;\n    }\n  }\n\n  /**\n   * Remove DuckDB secret for a connection\n   */\n  async disconnect(id: string): Promise<void> {\n    const conn = this.connections.get(id);\n    if (!conn) return;\n\n    const { connection: duckConn } = useDuckStore.getState();\n    if (!duckConn) return;\n\n    try {\n      const secretName = `cloud_${conn.id.replace(/-/g, \"_\")}`;\n      await duckConn.query(`DROP SECRET IF EXISTS ${secretName}`);\n    } catch (error) {\n      console.error(\"Failed to drop secret:\", error);\n    }\n\n    conn.isConnected = false;\n  }\n\n  /**\n   * Test a connection by trying to list bucket contents\n   */\n  async testConnection(id: string): Promise<{ success: boolean; error?: string }> {\n    const conn = this.connections.get(id);\n    if (!conn) {\n      return { success: false, error: \"Connection not found\" };\n    }\n\n    try {\n      // First ensure connected\n      if (!conn.isConnected) {\n        await this.connect(id);\n      }\n\n      const { connection: duckConn } = useDuckStore.getState();\n      if (!duckConn) {\n        return { success: false, error: \"No DuckDB connection\" };\n      }\n\n      // Try to list files in the bucket/container\n      let testQuery: string;\n      switch (conn.type) {\n        case \"s3\":\n          testQuery = `SELECT * FROM glob('s3://${conn.bucket}/*') LIMIT 1`;\n          break;\n        case \"gcs\":\n          testQuery = `SELECT * FROM glob('gcs://${conn.bucket}/*') LIMIT 1`;\n          break;\n        case \"azure\":\n          testQuery = `SELECT * FROM glob('azure://${conn.containerName}/*') LIMIT 1`;\n          break;\n      }\n\n      await duckConn.query(testQuery);\n      return { success: true };\n    } catch (error) {\n      const errorMsg = error instanceof Error ? error.message : String(error);\n      return { success: false, error: errorMsg };\n    }\n  }\n\n  /**\n   * Get the URI prefix for a connection\n   */\n  getUriPrefix(id: string): string | null {\n    const conn = this.connections.get(id);\n    if (!conn) return null;\n\n    switch (conn.type) {\n      case \"s3\":\n        return `s3://${conn.bucket}`;\n      case \"gcs\":\n        return `gcs://${conn.bucket}`;\n      case \"azure\":\n        return `azure://${conn.containerName}`;\n      default:\n        return null;\n    }\n  }\n}\n\n// Export singleton instance\nexport const cloudStorageService = new CloudStorageService();\n"
  },
  {
    "path": "src/lib/cloudStorage/testHttpfs.ts",
    "content": "/**\n * HTTPFS Feasibility Test Utility\n * Run these tests in browser console to check what works in DuckDB-WASM\n *\n * Usage: Import and call testHttpfsSupport() from browser console\n * Or: Access window.testHttpfs after importing this module\n */\n\nimport { useDuckStore } from \"@/store\";\n\nexport interface HttpfsTestResult {\n  test: string;\n  success: boolean;\n  error?: string;\n  result?: unknown;\n}\n\nexport async function testHttpfsSupport(): Promise<HttpfsTestResult[]> {\n  const results: HttpfsTestResult[] = [];\n  const { connection } = useDuckStore.getState();\n\n  if (!connection) {\n    return [{ test: \"Connection\", success: false, error: \"No DuckDB connection available\" }];\n  }\n\n  console.log(\"🦆 Starting HTTPFS Feasibility Tests...\\n\");\n\n  // Test 1: Check available extensions\n  try {\n    console.log(\"Test 1: Checking available extensions...\");\n    const extResult = await connection.query(`\n      SELECT extension_name, installed, loaded\n      FROM duckdb_extensions()\n      WHERE extension_name IN ('httpfs', 'aws', 'azure', 's3')\n    `);\n    const extensions = extResult.toArray().map((r: { toJSON: () => unknown }) => r.toJSON());\n    results.push({\n      test: \"Available Extensions\",\n      success: true,\n      result: extensions,\n    });\n    console.log(\"✅ Extensions check passed:\", extensions);\n  } catch (e) {\n    results.push({\n      test: \"Available Extensions\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"❌ Extensions check failed:\", e);\n  }\n\n  // Test 2: Try to INSTALL httpfs\n  try {\n    console.log(\"\\nTest 2: Installing httpfs...\");\n    await connection.query(`INSTALL httpfs`);\n    results.push({ test: \"INSTALL httpfs\", success: true });\n    console.log(\"✅ INSTALL httpfs succeeded\");\n  } catch (e) {\n    results.push({\n      test: \"INSTALL httpfs\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"❌ INSTALL httpfs failed:\", e);\n  }\n\n  // Test 3: Try to LOAD httpfs\n  try {\n    console.log(\"\\nTest 3: Loading httpfs...\");\n    await connection.query(`LOAD httpfs`);\n    results.push({ test: \"LOAD httpfs\", success: true });\n    console.log(\"✅ LOAD httpfs succeeded\");\n  } catch (e) {\n    results.push({\n      test: \"LOAD httpfs\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"❌ LOAD httpfs failed:\", e);\n  }\n\n  // Test 4: Check if httpfs functions exist\n  try {\n    console.log(\"\\nTest 4: Checking httpfs functions...\");\n    const funcsResult = await connection.query(`\n      SELECT function_name\n      FROM duckdb_functions()\n      WHERE function_name LIKE '%http%' OR function_name LIKE '%s3%'\n      LIMIT 10\n    `);\n    const funcs = funcsResult.toArray().map((r: { toJSON: () => unknown }) => r.toJSON());\n    results.push({\n      test: \"HTTPFS Functions\",\n      success: funcs.length > 0,\n      result: funcs,\n    });\n    console.log(\"✅ HTTPFS functions:\", funcs);\n  } catch (e) {\n    results.push({\n      test: \"HTTPFS Functions\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"❌ HTTPFS functions check failed:\", e);\n  }\n\n  // Test 5: Try to create a secret (even with dummy values)\n  try {\n    console.log(\"\\nTest 5: Creating S3 secret...\");\n    await connection.query(`\n      CREATE OR REPLACE SECRET test_s3_secret (\n        TYPE s3,\n        KEY_ID 'AKIAIOSFODNN7EXAMPLE',\n        SECRET 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',\n        REGION 'us-east-1'\n      )\n    `);\n    results.push({ test: \"CREATE SECRET (S3)\", success: true });\n    console.log(\"✅ CREATE SECRET succeeded\");\n\n    // Clean up\n    await connection.query(`DROP SECRET IF EXISTS test_s3_secret`);\n  } catch (e) {\n    results.push({\n      test: \"CREATE SECRET (S3)\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"❌ CREATE SECRET failed:\", e);\n  }\n\n  // Test 6: Try to read from a public HTTPS URL (CORS-enabled)\n  try {\n    console.log(\"\\nTest 6: Reading from HTTPS URL...\");\n    // Using a known CORS-enabled public parquet file\n    const httpsResult = await connection.query(`\n      SELECT count(*) as cnt FROM read_parquet('https://shell.duckdb.org/data/tpch/0_01/parquet/lineitem.parquet') LIMIT 1\n    `);\n    const count = httpsResult.toArray()[0]?.toJSON();\n    results.push({\n      test: \"HTTPS URL Read\",\n      success: true,\n      result: count,\n    });\n    console.log(\"✅ HTTPS URL read succeeded:\", count);\n  } catch (e) {\n    results.push({\n      test: \"HTTPS URL Read\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"❌ HTTPS URL read failed:\", e);\n  }\n\n  // Test 7: Try to read from S3 URL (likely to fail due to CORS)\n  try {\n    console.log(\"\\nTest 7: Reading from S3 URL...\");\n    // This will likely fail in browser due to CORS\n    const s3Result = await connection.query(`\n      SELECT count(*) as cnt FROM read_parquet('s3://test-bucket/test.parquet') LIMIT 1\n    `);\n    const count = s3Result.toArray()[0]?.toJSON();\n    results.push({\n      test: \"S3 URL Read\",\n      success: true,\n      result: count,\n    });\n    console.log(\"✅ S3 URL read succeeded:\", count);\n  } catch (e) {\n    results.push({\n      test: \"S3 URL Read\",\n      success: false,\n      error: e instanceof Error ? e.message : String(e),\n    });\n    console.log(\"⚠️ S3 URL read failed (expected in browser):\", e);\n  }\n\n  // Summary\n  console.log(\"\\n\" + \"=\".repeat(50));\n  console.log(\"📊 HTTPFS FEASIBILITY TEST SUMMARY\");\n  console.log(\"=\".repeat(50));\n\n  const passed = results.filter((r) => r.success).length;\n  const failed = results.filter((r) => !r.success).length;\n\n  results.forEach((r) => {\n    const icon = r.success ? \"✅\" : \"❌\";\n    console.log(`${icon} ${r.test}: ${r.success ? \"PASS\" : \"FAIL\"}`);\n    if (r.error) console.log(`   Error: ${r.error}`);\n  });\n\n  console.log(\"=\".repeat(50));\n  console.log(`Total: ${passed} passed, ${failed} failed`);\n  console.log(\"=\".repeat(50));\n\n  return results;\n}\n\n// Expose to window for easy console access\nif (typeof window !== \"undefined\") {\n  (window as unknown as Record<string, unknown>).testHttpfs = testHttpfsSupport;\n}\n\nexport default testHttpfsSupport;\n"
  },
  {
    "path": "src/lib/duckBrain/index.ts",
    "content": "// Duck Brain - Local AI Data Analyst for DuckUI\n// Powered by WebLLM (in-browser LLM inference)\n\nexport { duckBrainService } from \"./webllm.service\";\nexport type { ModelStatus, DuckBrainServiceState, StreamCallbacks } from \"./webllm.service\";\n\nexport { AVAILABLE_MODELS, DEFAULT_MODEL } from \"./models.config\";\nexport type { ModelConfig } from \"./models.config\";\n\nexport { formatSchemaForContext, getSchemaSummary } from \"./schemaFormatter\";\nexport type { SchemaContext } from \"./schemaFormatter\";\n\nexport { extractSQLFromResponse, formatSQLForDisplay } from \"./sqlParser\";\nexport type { ParsedSQLResult } from \"./sqlParser\";\n\nexport { buildTextToSQLMessages } from \"./prompts/text-to-sql\";\n"
  },
  {
    "path": "src/lib/duckBrain/models.config.ts",
    "content": "// Model configurations for Duck Brain\nexport interface ModelConfig {\n  id: string;\n  displayName: string;\n  size: string;\n  description: string;\n  contextLength: number;\n}\n\nexport const AVAILABLE_MODELS: ModelConfig[] = [\n  {\n    id: \"Phi-3.5-mini-instruct-q4f16_1-MLC\",\n    displayName: \"Phi-3.5 Mini\",\n    size: \"~2.3GB\",\n    description: \"Best balance of quality and performance for SQL generation\",\n    contextLength: 4096,\n  },\n  {\n    id: \"Llama-3.2-1B-Instruct-q4f16_1-MLC\",\n    displayName: \"Llama 3.2 1B\",\n    size: \"~1.1GB\",\n    description: \"Fastest option, good for quick queries\",\n    contextLength: 2048,\n  },\n  {\n    id: \"Qwen2.5-1.5B-Instruct-q4f16_1-MLC\",\n    displayName: \"Qwen 2.5 1.5B\",\n    size: \"~1GB\",\n    description: \"Good balance of size and capability\",\n    contextLength: 2048,\n  },\n];\n\nexport const DEFAULT_MODEL = AVAILABLE_MODELS[0];\n"
  },
  {
    "path": "src/lib/duckBrain/prompts/text-to-sql.ts",
    "content": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\nimport type { DuckBrainMessage } from \"@/store\";\n\nexport const TEXT_TO_SQL_SYSTEM_PROMPT = `You are Duck Brain, a DuckDB SQL query generator.\n\nRULES:\n1. Output ONLY the SQL query - no explanations, no markdown code fences\n2. Use DuckDB syntax (similar to PostgreSQL)\n3. ONLY use tables and columns shown in the DATABASE SCHEMA below\n4. If a table or column doesn't exist in the schema, don't use it\n5. Always check the schema for correct table and column names\n\nDuckDB Syntax Tips:\n- Use ILIKE for case-insensitive matching\n- LIMIT goes at the end: SELECT * FROM table LIMIT 10\n- String literals use single quotes: 'value'\n- Use || for string concatenation\n- SAMPLE for random rows: SELECT * FROM table USING SAMPLE 10\n- Date functions: date_trunc('month', col), date_part('year', col)\n- GROUP BY ALL to group by all non-aggregated columns\n\nIMPORTANT: Start your response directly with SELECT, INSERT, UPDATE, DELETE, CREATE, WITH, SHOW, DESCRIBE, or other SQL keywords. No explanations, no markdown.`;\n\n/**\n * Builds context from previous queries and their results.\n * This allows the model to see what data was returned and iterate on it.\n */\nexport function buildResultsContext(messages: DuckBrainMessage[]): string {\n  // Find recent messages with successful query results\n  const messagesWithResults = messages.filter(\n    (m) =>\n      m.role === \"assistant\" && m.sql && m.queryResult?.status === \"success\" && m.queryResult.data\n  );\n\n  // Take the last 3 results to avoid context overflow\n  const recentResults = messagesWithResults.slice(-3);\n\n  if (recentResults.length === 0) {\n    return \"\";\n  }\n\n  const contextParts = recentResults.map((m) => {\n    const result = m.queryResult!.data!;\n    const preview = result.data.slice(0, 10); // First 10 rows\n    const columnInfo = result.columns\n      .map((col, i) => `${col} (${result.columnTypes[i]})`)\n      .join(\", \");\n\n    return `Previous Query: ${m.sql}\nColumns: ${columnInfo}\nRow Count: ${result.rowCount}\nSample Data (first ${Math.min(10, result.rowCount)} rows):\n${JSON.stringify(preview, null, 2)}`;\n  });\n\n  return `\\n\\n--- PREVIOUS QUERY RESULTS ---\\nYou can reference these results to write follow-up queries or analyze the data further.\\n\\n${contextParts.join(\"\\n\\n---\\n\\n\")}`;\n}\n\nexport const DUCKDB_FEW_SHOT_EXAMPLES: ChatCompletionMessageParam[] = [\n  {\n    role: \"user\",\n    content: \"Show me the top 5 customers by total orders\",\n  },\n  {\n    role: \"assistant\",\n    content:\n      \"SELECT customer_id, COUNT(*) as total_orders FROM orders GROUP BY customer_id ORDER BY total_orders DESC LIMIT 5\",\n  },\n  {\n    role: \"user\",\n    content: \"Find duplicate emails\",\n  },\n  {\n    role: \"assistant\",\n    content: \"SELECT email, COUNT(*) as count FROM users GROUP BY email HAVING COUNT(*) > 1\",\n  },\n  {\n    role: \"user\",\n    content: \"Calculate month over month revenue growth\",\n  },\n  {\n    role: \"assistant\",\n    content: `SELECT\n  date_trunc('month', order_date) as month,\n  SUM(amount) as revenue,\n  LAG(SUM(amount)) OVER (ORDER BY date_trunc('month', order_date)) as prev_month,\n  ROUND((SUM(amount) - LAG(SUM(amount)) OVER (ORDER BY date_trunc('month', order_date))) / LAG(SUM(amount)) OVER (ORDER BY date_trunc('month', order_date)) * 100, 2) as growth_pct\nFROM orders\nGROUP BY date_trunc('month', order_date)\nORDER BY month`,\n  },\n  {\n    role: \"user\",\n    content: \"Sample 100 random rows from the transactions table\",\n  },\n  {\n    role: \"assistant\",\n    content: \"SELECT * FROM transactions USING SAMPLE 100\",\n  },\n];\n\nexport function buildTextToSQLMessages(\n  userQuery: string,\n  schemaContext: string,\n  previousMessages: DuckBrainMessage[] = [],\n  includeFewShot: boolean = true\n): ChatCompletionMessageParam[] {\n  // Build results context from previous queries\n  const resultsContext = buildResultsContext(previousMessages);\n\n  const messages: ChatCompletionMessageParam[] = [\n    {\n      role: \"system\",\n      content: `${TEXT_TO_SQL_SYSTEM_PROMPT}\\n\\n${schemaContext}${resultsContext}`,\n    },\n  ];\n\n  if (includeFewShot) {\n    messages.push(...DUCKDB_FEW_SHOT_EXAMPLES);\n  }\n\n  messages.push({\n    role: \"user\",\n    content: userQuery,\n  });\n\n  return messages;\n}\n"
  },
  {
    "path": "src/lib/duckBrain/providers/anthropic.provider.ts",
    "content": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\nimport type {\n  AIProvider,\n  ProviderConfig,\n  StreamCallbacks,\n  GenerationOptions,\n  ProviderStatus,\n} from \"./types\";\n\n/**\n * Anthropic (Claude) API Provider\n * Uses fetch for API calls - no SDK dependency\n */\nexport class AnthropicProvider implements AIProvider {\n  readonly name = \"anthropic\" as const;\n\n  private apiKey: string = \"\";\n  private modelId: string = \"claude-sonnet-4-20250514\";\n  private abortController: AbortController | null = null;\n  private ready: boolean = false;\n  private initializing: boolean = false;\n  private error: string | undefined;\n\n  async initialize(config: ProviderConfig): Promise<void> {\n    this.initializing = true;\n    this.error = undefined;\n\n    try {\n      if (!config.apiKey) {\n        throw new Error(\"Anthropic API key is required\");\n      }\n\n      this.apiKey = config.apiKey;\n      this.modelId = config.modelId || \"claude-sonnet-4-20250514\";\n\n      // Note: Anthropic doesn't have a simple \"list models\" endpoint\n      // We'll validate on first request instead\n      this.ready = true;\n    } catch (err) {\n      this.error = err instanceof Error ? err.message : \"Failed to initialize Anthropic\";\n      this.ready = false;\n      throw err;\n    } finally {\n      this.initializing = false;\n    }\n  }\n\n  async generateStreaming(\n    messages: ChatCompletionMessageParam[],\n    callbacks: StreamCallbacks,\n    options?: GenerationOptions\n  ): Promise<void> {\n    if (!this.ready) {\n      throw new Error(\"Anthropic provider not initialized\");\n    }\n\n    this.abortController = new AbortController();\n\n    // Convert messages to Anthropic format\n    const systemMessage = messages.find((m) => m.role === \"system\");\n    const nonSystemMessages = messages.filter((m) => m.role !== \"system\");\n\n    try {\n      const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          \"x-api-key\": this.apiKey,\n          \"anthropic-version\": \"2023-06-01\",\n          \"anthropic-dangerous-direct-browser-access\": \"true\",\n        },\n        body: JSON.stringify({\n          model: this.modelId,\n          max_tokens: options?.maxTokens || 2048,\n          system: systemMessage?.content || undefined,\n          messages: nonSystemMessages.map((m) => ({\n            role: m.role === \"assistant\" ? \"assistant\" : \"user\",\n            content: m.content,\n          })),\n          stream: true,\n        }),\n        signal: this.abortController.signal,\n      });\n\n      if (!response.ok) {\n        const error = await response.json().catch(() => ({}));\n        throw new Error(error.error?.message || `API error: ${response.status}`);\n      }\n\n      const reader = response.body?.getReader();\n      if (!reader) {\n        throw new Error(\"No response body\");\n      }\n\n      const decoder = new TextDecoder();\n      let fullText = \"\";\n\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n\n        const chunk = decoder.decode(value, { stream: true });\n        const lines = chunk.split(\"\\n\").filter((line) => line.trim() !== \"\");\n\n        for (const line of lines) {\n          if (line.startsWith(\"data: \")) {\n            const data = line.slice(6);\n\n            try {\n              const parsed = JSON.parse(data);\n\n              // Handle different event types\n              if (parsed.type === \"content_block_delta\") {\n                const content = parsed.delta?.text;\n                if (content) {\n                  fullText += content;\n                  callbacks.onToken?.(content);\n                }\n              } else if (parsed.type === \"message_stop\") {\n                // Message complete\n              } else if (parsed.type === \"error\") {\n                throw new Error(parsed.error?.message || \"Unknown error\");\n              }\n            } catch (parseErr) {\n              // Re-throw actual API errors (from parsed.type === \"error\" above)\n              if (\n                parseErr instanceof Error &&\n                parseErr.message !== \"Unexpected end of JSON input\" &&\n                !parseErr.message.startsWith(\"Unexpected token\")\n              ) {\n                throw parseErr;\n              }\n              // Otherwise ignore JSON parse errors for incomplete SSE chunks\n            }\n          }\n        }\n      }\n\n      callbacks.onComplete?.(fullText);\n    } catch (err) {\n      if (err instanceof Error && err.name === \"AbortError\") {\n        return;\n      }\n      const error = err instanceof Error ? err : new Error(\"Generation failed\");\n      callbacks.onError?.(error);\n      throw error;\n    } finally {\n      this.abortController = null;\n    }\n  }\n\n  async generateText(\n    messages: ChatCompletionMessageParam[],\n    options?: GenerationOptions\n  ): Promise<string> {\n    if (!this.ready) {\n      throw new Error(\"Anthropic provider not initialized\");\n    }\n\n    // Convert messages to Anthropic format\n    const systemMessage = messages.find((m) => m.role === \"system\");\n    const nonSystemMessages = messages.filter((m) => m.role !== \"system\");\n\n    const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        \"x-api-key\": this.apiKey,\n        \"anthropic-version\": \"2023-06-01\",\n        \"anthropic-dangerous-direct-browser-access\": \"true\",\n      },\n      body: JSON.stringify({\n        model: this.modelId,\n        max_tokens: options?.maxTokens || 2048,\n        system: systemMessage?.content || undefined,\n        messages: nonSystemMessages.map((m) => ({\n          role: m.role === \"assistant\" ? \"assistant\" : \"user\",\n          content: m.content,\n        })),\n      }),\n    });\n\n    if (!response.ok) {\n      const error = await response.json().catch(() => ({}));\n      throw new Error(error.error?.message || `API error: ${response.status}`);\n    }\n\n    const data = await response.json();\n    return data.content?.[0]?.text || \"\";\n  }\n\n  abort(): void {\n    this.abortController?.abort();\n  }\n\n  async cleanup(): Promise<void> {\n    this.abort();\n    this.ready = false;\n    this.apiKey = \"\";\n  }\n\n  getStatus(): ProviderStatus {\n    return {\n      ready: this.ready,\n      initializing: this.initializing,\n      error: this.error,\n      currentModel: this.modelId,\n    };\n  }\n\n  isReady(): boolean {\n    return this.ready;\n  }\n}\n"
  },
  {
    "path": "src/lib/duckBrain/providers/index.ts",
    "content": "export * from \"./types\";\nexport { OpenAIProvider } from \"./openai.provider\";\nexport { AnthropicProvider } from \"./anthropic.provider\";\n\nimport type { AIProvider, AIProviderType, ProviderConfig } from \"./types\";\nimport { OpenAIProvider } from \"./openai.provider\";\nimport { AnthropicProvider } from \"./anthropic.provider\";\n\n/**\n * Factory function to create AI provider instances\n */\nexport function createProvider(type: AIProviderType): AIProvider {\n  switch (type) {\n    case \"openai\":\n    case \"openai-compatible\":\n      return new OpenAIProvider();\n    case \"anthropic\":\n      return new AnthropicProvider();\n    case \"webllm\":\n      // WebLLM uses the existing service, not this factory\n      throw new Error(\"WebLLM should use the existing duckBrainService\");\n    case \"gemini\":\n      throw new Error(\"Gemini provider not yet implemented\");\n    default:\n      throw new Error(`Unknown provider type: ${type}`);\n  }\n}\n\n/**\n * Test provider connection with given config\n */\nexport async function testProviderConnection(\n  type: AIProviderType,\n  config: ProviderConfig\n): Promise<{ success: boolean; error?: string }> {\n  if (type === \"webllm\") {\n    return { success: true }; // WebLLM doesn't need API key testing\n  }\n\n  try {\n    const provider = createProvider(type);\n    await provider.initialize(config);\n    await provider.cleanup();\n    return { success: true };\n  } catch (err) {\n    return {\n      success: false,\n      error: err instanceof Error ? err.message : \"Connection failed\",\n    };\n  }\n}\n"
  },
  {
    "path": "src/lib/duckBrain/providers/openai.provider.ts",
    "content": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\nimport type {\n  AIProvider,\n  ProviderConfig,\n  StreamCallbacks,\n  GenerationOptions,\n  ProviderStatus,\n} from \"./types\";\n\n/**\n * OpenAI API Provider\n * Uses fetch for API calls - no SDK dependency\n */\nexport class OpenAIProvider implements AIProvider {\n  readonly name = \"openai\" as const;\n\n  private apiKey: string = \"\";\n  private modelId: string = \"gpt-4o-mini\";\n  private baseUrl: string = \"https://api.openai.com/v1\";\n  private abortController: AbortController | null = null;\n  private ready: boolean = false;\n  private initializing: boolean = false;\n  private error: string | undefined;\n\n  async initialize(config: ProviderConfig): Promise<void> {\n    this.initializing = true;\n    this.error = undefined;\n\n    const isCustomEndpoint = config.baseUrl && config.baseUrl !== \"https://api.openai.com/v1\";\n\n    try {\n      // API key is required for OpenAI, optional for custom endpoints (e.g., Ollama)\n      if (!config.apiKey && !isCustomEndpoint) {\n        throw new Error(\"OpenAI API key is required\");\n      }\n\n      this.apiKey = config.apiKey || \"\";\n      this.modelId = config.modelId || (isCustomEndpoint ? \"llama3.2\" : \"gpt-4o-mini\");\n      this.baseUrl = config.baseUrl || \"https://api.openai.com/v1\";\n\n      // Test the connection\n      const headers: Record<string, string> = {\n        \"Content-Type\": \"application/json\",\n      };\n      if (this.apiKey) {\n        headers.Authorization = `Bearer ${this.apiKey}`;\n      }\n\n      if (isCustomEndpoint) {\n        // For custom endpoints, test with a minimal chat completion request\n        // since /v1/models may not exist on all compatible APIs\n        const testResponse = await fetch(`${this.baseUrl}/chat/completions`, {\n          method: \"POST\",\n          headers,\n          body: JSON.stringify({\n            model: this.modelId,\n            messages: [{ role: \"user\", content: \"test\" }],\n            max_tokens: 1,\n          }),\n        });\n\n        if (!testResponse.ok) {\n          const error = await testResponse.json().catch(() => ({}));\n          throw new Error(error.error?.message || `Connection failed: ${testResponse.status}`);\n        }\n      } else {\n        // For OpenAI, use the models endpoint\n        const response = await fetch(`${this.baseUrl}/models`, { headers });\n\n        if (!response.ok) {\n          const error = await response.json().catch(() => ({}));\n          throw new Error(error.error?.message || `API error: ${response.status}`);\n        }\n      }\n\n      this.ready = true;\n    } catch (err) {\n      this.error = err instanceof Error ? err.message : \"Failed to initialize OpenAI\";\n      this.ready = false;\n      throw err;\n    } finally {\n      this.initializing = false;\n    }\n  }\n\n  async generateStreaming(\n    messages: ChatCompletionMessageParam[],\n    callbacks: StreamCallbacks,\n    options?: GenerationOptions\n  ): Promise<void> {\n    if (!this.ready) {\n      throw new Error(\"OpenAI provider not initialized\");\n    }\n\n    this.abortController = new AbortController();\n    let fullText = \"\";\n\n    try {\n      const headers: Record<string, string> = {\n        \"Content-Type\": \"application/json\",\n      };\n      if (this.apiKey) {\n        headers.Authorization = `Bearer ${this.apiKey}`;\n      }\n\n      const response = await fetch(`${this.baseUrl}/chat/completions`, {\n        method: \"POST\",\n        headers,\n        body: JSON.stringify({\n          model: this.modelId,\n          messages: messages.map((m) => ({\n            role: m.role,\n            content: m.content,\n          })),\n          stream: true,\n          max_tokens: options?.maxTokens || 2048,\n          temperature: options?.temperature || 0.7,\n          stop: options?.stopSequences,\n        }),\n        signal: this.abortController.signal,\n      });\n\n      if (!response.ok) {\n        const error = await response.json().catch(() => ({}));\n        throw new Error(error.error?.message || `API error: ${response.status}`);\n      }\n\n      const reader = response.body?.getReader();\n      if (!reader) {\n        throw new Error(\"No response body\");\n      }\n\n      const decoder = new TextDecoder();\n\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n\n        const chunk = decoder.decode(value, { stream: true });\n        const lines = chunk.split(\"\\n\").filter((line) => line.trim() !== \"\");\n\n        for (const line of lines) {\n          if (line.startsWith(\"data: \")) {\n            const data = line.slice(6);\n            if (data === \"[DONE]\") continue;\n\n            try {\n              const parsed = JSON.parse(data);\n              const content = parsed.choices?.[0]?.delta?.content;\n              if (content) {\n                fullText += content;\n                callbacks.onToken?.(content);\n              }\n            } catch {\n              // Ignore parse errors for incomplete chunks\n            }\n          }\n        }\n      }\n\n      callbacks.onComplete?.(fullText);\n    } catch (err) {\n      if (err instanceof Error && err.name === \"AbortError\") {\n        callbacks.onComplete?.(fullText || \"\");\n        return;\n      }\n      const error = err instanceof Error ? err : new Error(\"Generation failed\");\n      callbacks.onError?.(error);\n      throw error;\n    } finally {\n      this.abortController = null;\n    }\n  }\n\n  async generateText(\n    messages: ChatCompletionMessageParam[],\n    options?: GenerationOptions\n  ): Promise<string> {\n    if (!this.ready) {\n      throw new Error(\"OpenAI provider not initialized\");\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/json\",\n    };\n    if (this.apiKey) {\n      headers.Authorization = `Bearer ${this.apiKey}`;\n    }\n\n    const response = await fetch(`${this.baseUrl}/chat/completions`, {\n      method: \"POST\",\n      headers,\n      body: JSON.stringify({\n        model: this.modelId,\n        messages: messages.map((m) => ({\n          role: m.role,\n          content: m.content,\n        })),\n        stream: false,\n        max_tokens: options?.maxTokens || 2048,\n        temperature: options?.temperature || 0.7,\n        stop: options?.stopSequences,\n      }),\n    });\n\n    if (!response.ok) {\n      const error = await response.json().catch(() => ({}));\n      throw new Error(error.error?.message || `API error: ${response.status}`);\n    }\n\n    const data = await response.json();\n    return data.choices?.[0]?.message?.content || \"\";\n  }\n\n  abort(): void {\n    this.abortController?.abort();\n  }\n\n  async cleanup(): Promise<void> {\n    this.abort();\n    this.ready = false;\n    this.apiKey = \"\";\n  }\n\n  getStatus(): ProviderStatus {\n    return {\n      ready: this.ready,\n      initializing: this.initializing,\n      error: this.error,\n      currentModel: this.modelId,\n    };\n  }\n\n  isReady(): boolean {\n    return this.ready;\n  }\n}\n"
  },
  {
    "path": "src/lib/duckBrain/providers/types.ts",
    "content": "import type { ChatCompletionMessageParam } from \"@mlc-ai/web-llm\";\n\nexport type AIProviderType = \"webllm\" | \"openai\" | \"anthropic\" | \"gemini\" | \"openai-compatible\";\n\nexport interface ProviderConfig {\n  apiKey?: string;\n  modelId?: string;\n  baseUrl?: string; // For OpenAI-compatible APIs\n}\n\nexport interface StreamCallbacks {\n  onToken?: (token: string) => void;\n  onComplete?: (fullText: string) => void;\n  onError?: (error: Error) => void;\n}\n\nexport interface GenerationOptions {\n  maxTokens?: number;\n  temperature?: number;\n  stopSequences?: string[];\n}\n\nexport interface ProviderStatus {\n  ready: boolean;\n  initializing: boolean;\n  error?: string;\n  currentModel?: string;\n}\n\n/**\n * Abstract interface for AI providers\n * All providers (WebLLM, OpenAI, Claude, etc.) implement this\n */\nexport interface AIProvider {\n  readonly name: AIProviderType;\n\n  /**\n   * Initialize the provider with config\n   */\n  initialize(config: ProviderConfig): Promise<void>;\n\n  /**\n   * Generate text with streaming support\n   */\n  generateStreaming(\n    messages: ChatCompletionMessageParam[],\n    callbacks: StreamCallbacks,\n    options?: GenerationOptions\n  ): Promise<void>;\n\n  /**\n   * Generate text without streaming (returns full response)\n   */\n  generateText(\n    messages: ChatCompletionMessageParam[],\n    options?: GenerationOptions\n  ): Promise<string>;\n\n  /**\n   * Abort any ongoing generation\n   */\n  abort(): void;\n\n  /**\n   * Clean up resources\n   */\n  cleanup(): Promise<void>;\n\n  /**\n   * Get current status\n   */\n  getStatus(): ProviderStatus;\n\n  /**\n   * Check if provider is ready\n   */\n  isReady(): boolean;\n}\n\n/**\n * Available models for each provider\n */\nexport interface ModelOption {\n  id: string;\n  name: string;\n  description?: string;\n  contextLength?: number;\n}\n\nexport const OPENAI_MODELS: ModelOption[] = [\n  {\n    id: \"gpt-4o\",\n    name: \"GPT-4o\",\n    description: \"Most capable, best for complex tasks\",\n    contextLength: 128000,\n  },\n  {\n    id: \"gpt-4o-mini\",\n    name: \"GPT-4o Mini\",\n    description: \"Fast and affordable\",\n    contextLength: 128000,\n  },\n  {\n    id: \"gpt-4-turbo\",\n    name: \"GPT-4 Turbo\",\n    description: \"GPT-4 with vision\",\n    contextLength: 128000,\n  },\n  {\n    id: \"gpt-3.5-turbo\",\n    name: \"GPT-3.5 Turbo\",\n    description: \"Fast, good for simple tasks\",\n    contextLength: 16385,\n  },\n];\n\nexport const ANTHROPIC_MODELS: ModelOption[] = [\n  {\n    id: \"claude-sonnet-4-20250514\",\n    name: \"Claude Sonnet 4\",\n    description: \"Best balance of speed and capability\",\n    contextLength: 200000,\n  },\n  {\n    id: \"claude-3-5-sonnet-20241022\",\n    name: \"Claude 3.5 Sonnet\",\n    description: \"Fast and capable\",\n    contextLength: 200000,\n  },\n  {\n    id: \"claude-3-5-haiku-20241022\",\n    name: \"Claude 3.5 Haiku\",\n    description: \"Fastest, most affordable\",\n    contextLength: 200000,\n  },\n];\n"
  },
  {
    "path": "src/lib/duckBrain/schemaFormatter.ts",
    "content": "import type { DatabaseInfo } from \"@/store\";\n\nexport interface SchemaContext {\n  formatted: string;\n  tableCount: number;\n  columnCount: number;\n  truncated: boolean;\n}\n\n/**\n * Formats database schema into a context string for the LLM\n */\nexport function formatSchemaForContext(\n  databases: DatabaseInfo[],\n  options?: {\n    maxTables?: number;\n    maxColumnsPerTable?: number;\n    maxContextLength?: number;\n  }\n): SchemaContext {\n  const maxTables = options?.maxTables ?? 20;\n  const maxColumnsPerTable = options?.maxColumnsPerTable ?? 15;\n  const maxContextLength = options?.maxContextLength ?? 3000;\n\n  let tableCount = 0;\n  let columnCount = 0;\n  let truncated = false;\n  const parts: string[] = [];\n\n  parts.push(\"DATABASE SCHEMA:\");\n  parts.push(\"The following tables are available in your DuckDB database:\\n\");\n\n  for (const db of databases) {\n    if (tableCount >= maxTables) {\n      truncated = true;\n      break;\n    }\n\n    for (const table of db.tables) {\n      if (tableCount >= maxTables) {\n        truncated = true;\n        break;\n      }\n\n      const tableName = db.name === \"memory\" ? table.name : `${db.name}.${table.name}`;\n      const columns = table.columns.slice(0, maxColumnsPerTable);\n      const columnDefs = columns\n        .map((col) => `  ${col.name} ${col.type}${col.nullable ? \"\" : \" NOT NULL\"}`)\n        .join(\",\\n\");\n\n      parts.push(`CREATE TABLE ${tableName} (`);\n      parts.push(columnDefs);\n      parts.push(\");\");\n\n      if (table.columns.length > maxColumnsPerTable) {\n        parts.push(`-- ... and ${table.columns.length - maxColumnsPerTable} more columns`);\n      }\n\n      if (table.rowCount > 0) {\n        parts.push(`-- Approximately ${table.rowCount.toLocaleString()} rows`);\n      }\n\n      parts.push(\"\");\n      tableCount++;\n      columnCount += columns.length;\n    }\n  }\n\n  if (truncated) {\n    parts.push(\"-- [Schema truncated, more tables available]\");\n  }\n\n  let formatted = parts.join(\"\\n\");\n\n  if (formatted.length > maxContextLength) {\n    formatted = formatted.slice(0, maxContextLength) + \"\\n-- [Schema truncated for context limit]\";\n    truncated = true;\n  }\n\n  return { formatted, tableCount, columnCount, truncated };\n}\n\n/**\n * Gets a summary of the schema for display purposes\n */\nexport function getSchemaSummary(databases: DatabaseInfo[]): string {\n  let totalTables = 0;\n  let totalColumns = 0;\n\n  for (const db of databases) {\n    totalTables += db.tables.length;\n    for (const table of db.tables) {\n      totalColumns += table.columns.length;\n    }\n  }\n\n  return `${totalTables} table${totalTables !== 1 ? \"s\" : \"\"}, ${totalColumns} column${totalColumns !== 1 ? \"s\" : \"\"}`;\n}\n"
  },
  {
    "path": "src/lib/duckBrain/sqlParser.ts",
    "content": "export interface ParsedSQLResult {\n  sql: string | null;\n  confidence: number;\n  issues: string[];\n}\n\nconst SQL_KEYWORDS = [\n  \"SELECT\",\n  \"INSERT\",\n  \"UPDATE\",\n  \"DELETE\",\n  \"CREATE\",\n  \"DROP\",\n  \"ALTER\",\n  \"WITH\",\n  \"SHOW\",\n  \"DESCRIBE\",\n  \"EXPLAIN\",\n  \"PRAGMA\",\n  \"COPY\",\n  \"EXPORT\",\n  \"IMPORT\",\n];\n\n/**\n * Extracts clean SQL from an LLM response\n * Handles markdown code blocks, explanatory prefixes, and trailing text\n */\nexport function extractSQLFromResponse(response: string): ParsedSQLResult {\n  const issues: string[] = [];\n  let cleaned = response.trim();\n\n  // 1. Extract ALL code blocks and find the best SQL one\n  const codeBlockRegex = /```(?:sql|SQL)?\\s*([\\s\\S]*?)```/g;\n  const codeBlocks: string[] = [];\n  let match;\n  while ((match = codeBlockRegex.exec(cleaned)) !== null) {\n    const blockContent = match[1].trim();\n    if (blockContent) {\n      codeBlocks.push(blockContent);\n    }\n  }\n\n  // If we found code blocks, pick the best one (the one that looks most like SQL)\n  if (codeBlocks.length > 0) {\n    // Find a code block that starts with a SQL keyword\n    const bestBlock = codeBlocks.find((block) =>\n      SQL_KEYWORDS.some((kw) => block.toUpperCase().startsWith(kw))\n    );\n\n    if (bestBlock) {\n      cleaned = bestBlock;\n      issues.push(\"Extracted from code block\");\n    } else {\n      // Fallback: use the longest code block (likely the SQL)\n      cleaned = codeBlocks.reduce((a, b) => (a.length > b.length ? a : b));\n      issues.push(\"Used longest code block\");\n    }\n  }\n\n  // 2. Remove common prefixes the model might add (only if not from code block)\n  if (codeBlocks.length === 0) {\n    const prefixPatterns = [\n      /^(?:Here(?:'s| is)(?: the)? (?:SQL|query)[:\\s]*)/i,\n      /^(?:The (?:SQL|query) (?:is|would be)[:\\s]*)/i,\n      /^(?:SQL[:\\s]+)/i,\n      /^(?:Query[:\\s]+)/i,\n      /^(?:Try this[:\\s]*)/i,\n      /^(?:You can use[:\\s]*)/i,\n      /^(?:Sure[,!]?\\s*(?:here(?:'s| is)[:\\s]*)?)/i,\n      /^(?:Certainly[,!]?\\s*(?:here(?:'s| is)[:\\s]*)?)/i,\n    ];\n\n    for (const pattern of prefixPatterns) {\n      if (pattern.test(cleaned)) {\n        cleaned = cleaned.replace(pattern, \"\").trim();\n        issues.push(\"Removed explanatory prefix\");\n        break;\n      }\n    }\n  }\n\n  // 3. If no code blocks were found, try to extract SQL from the text\n  // Look for SQL that starts with a keyword and try to find its end\n  if (codeBlocks.length === 0) {\n    const sqlStartMatch = cleaned.match(new RegExp(`(${SQL_KEYWORDS.join(\"|\")})\\\\b`, \"i\"));\n\n    if (sqlStartMatch && sqlStartMatch.index !== undefined) {\n      // Start from the SQL keyword\n      let sqlPart = cleaned.slice(sqlStartMatch.index);\n\n      // Try to find the end of the SQL (semicolon followed by non-SQL text)\n      const semicolonIndex = sqlPart.indexOf(\";\");\n      if (semicolonIndex !== -1) {\n        // Check if there's text after the semicolon that looks like an explanation\n        const afterSemicolon = sqlPart.slice(semicolonIndex + 1).trim();\n        const looksLikeExplanation =\n          /^(This|Note|The above|It |I |You |Where |Which |Here |--|\\n\\n)/i.test(afterSemicolon);\n\n        if (looksLikeExplanation || !afterSemicolon) {\n          // Include up to and including the semicolon\n          sqlPart = sqlPart.slice(0, semicolonIndex + 1);\n          if (afterSemicolon) {\n            issues.push(\"Trimmed trailing explanation after semicolon\");\n          }\n        }\n      }\n\n      cleaned = sqlPart.trim();\n    }\n  }\n\n  // 4. Final cleanup - remove any remaining markdown artifacts\n  cleaned = cleaned.replace(/^`+|`+$/g, \"\").trim();\n\n  // 5. Validate it looks like SQL\n  const startsWithKeyword = SQL_KEYWORDS.some((kw) => cleaned.toUpperCase().startsWith(kw));\n\n  if (!startsWithKeyword) {\n    issues.push(\"Does not start with expected SQL keyword\");\n    return { sql: null, confidence: 0, issues };\n  }\n\n  // 6. Basic SQL validation\n  if (cleaned.toUpperCase().startsWith(\"SELECT\")) {\n    const hasFrom = /\\bFROM\\b/i.test(cleaned) || /SELECT\\s+[\\d+\\-*/() ]+;?\\s*$/i.test(cleaned); // Allow SELECT 1+1 etc\n    if (!hasFrom) {\n      issues.push(\"SELECT query might be missing FROM clause\");\n    }\n  }\n\n  // Check for potentially incomplete statements\n  const openParens = (cleaned.match(/\\(/g) || []).length;\n  const closeParens = (cleaned.match(/\\)/g) || []).length;\n  if (openParens !== closeParens) {\n    issues.push(\"Mismatched parentheses - query may be incomplete\");\n  }\n\n  // 7. Calculate confidence score\n  let confidence = 1.0;\n  confidence -= issues.length * 0.1;\n  confidence = Math.max(0.1, confidence);\n\n  return { sql: cleaned, confidence, issues };\n}\n\n/**\n * Formats SQL for display with basic indentation\n */\nexport function formatSQLForDisplay(sql: string): string {\n  return sql\n    .replace(/\\bSELECT\\b/gi, \"SELECT\")\n    .replace(/\\bFROM\\b/gi, \"\\nFROM\")\n    .replace(/\\bWHERE\\b/gi, \"\\nWHERE\")\n    .replace(/\\bAND\\b/gi, \"\\n  AND\")\n    .replace(/\\bOR\\b/gi, \"\\n  OR\")\n    .replace(/\\bGROUP BY\\b/gi, \"\\nGROUP BY\")\n    .replace(/\\bORDER BY\\b/gi, \"\\nORDER BY\")\n    .replace(/\\bHAVING\\b/gi, \"\\nHAVING\")\n    .replace(/\\bLIMIT\\b/gi, \"\\nLIMIT\")\n    .replace(/\\bJOIN\\b/gi, \"\\nJOIN\")\n    .replace(/\\bLEFT JOIN\\b/gi, \"\\nLEFT JOIN\")\n    .replace(/\\bRIGHT JOIN\\b/gi, \"\\nRIGHT JOIN\")\n    .replace(/\\bINNER JOIN\\b/gi, \"\\nINNER JOIN\")\n    .replace(/\\bON\\b/gi, \"\\n  ON\");\n}\n"
  },
  {
    "path": "src/lib/duckBrain/webllm.service.ts",
    "content": "import {\n  CreateWebWorkerMLCEngine,\n  WebWorkerMLCEngine,\n  InitProgressReport,\n  ChatCompletionMessageParam,\n} from \"@mlc-ai/web-llm\";\nimport { DEFAULT_MODEL } from \"./models.config\";\n\n// Extend Navigator type for WebGPU\ninterface GPUAdapter {\n  readonly name: string;\n}\n\ninterface GPUInterface {\n  requestAdapter(): Promise<GPUAdapter | null>;\n}\n\ndeclare global {\n  interface Navigator {\n    gpu?: GPUInterface;\n  }\n}\n\nexport type ModelStatus = \"idle\" | \"checking\" | \"downloading\" | \"loading\" | \"ready\" | \"error\";\n\nexport interface DuckBrainServiceState {\n  status: ModelStatus;\n  currentModel: string | null;\n  downloadProgress: number;\n  downloadStatus: string;\n  error: string | null;\n  isWebGPUSupported: boolean | null;\n}\n\nexport interface StreamCallbacks {\n  onToken: (token: string, fullText: string) => void;\n  onComplete: (fullText: string) => void;\n  onError: (error: Error) => void;\n}\n\ntype StateListener = (state: DuckBrainServiceState) => void;\n\n/**\n * Singleton service for managing WebLLM engine\n * Runs inference in a Web Worker to keep UI responsive\n */\nclass DuckBrainService {\n  private engine: WebWorkerMLCEngine | null = null;\n  private worker: Worker | null = null;\n  private abortController: AbortController | null = null;\n  private stateListeners: Set<StateListener> = new Set();\n\n  private state: DuckBrainServiceState = {\n    status: \"idle\",\n    currentModel: null,\n    downloadProgress: 0,\n    downloadStatus: \"\",\n    error: null,\n    isWebGPUSupported: null,\n  };\n\n  constructor() {\n    // Check WebGPU support on construction\n    this.checkWebGPUSupport();\n  }\n\n  /**\n   * Check if WebGPU is available in the browser\n   */\n  async checkWebGPUSupport(): Promise<boolean> {\n    this.updateState({ status: \"checking\" });\n\n    if (!navigator.gpu) {\n      this.updateState({\n        isWebGPUSupported: false,\n        status: \"error\",\n        error: \"WebGPU is not supported in this browser. Please use Chrome 113+ or Edge 113+.\",\n      });\n      return false;\n    }\n\n    try {\n      const adapter = await navigator.gpu.requestAdapter();\n      const supported = adapter !== null;\n\n      this.updateState({\n        isWebGPUSupported: supported,\n        status: supported ? \"idle\" : \"error\",\n        error: supported ? null : \"WebGPU adapter not available. Your GPU may not be supported.\",\n      });\n\n      return supported;\n    } catch (error) {\n      this.updateState({\n        isWebGPUSupported: false,\n        status: \"error\",\n        error: \"Failed to initialize WebGPU: \" + (error as Error).message,\n      });\n      return false;\n    }\n  }\n\n  /**\n   * Update state and notify listeners\n   */\n  private updateState(partial: Partial<DuckBrainServiceState>) {\n    this.state = { ...this.state, ...partial };\n    this.stateListeners.forEach((listener) => listener(this.state));\n  }\n\n  /**\n   * Subscribe to state changes\n   */\n  subscribe(listener: StateListener): () => void {\n    this.stateListeners.add(listener);\n    // Immediately call with current state\n    listener(this.state);\n    return () => this.stateListeners.delete(listener);\n  }\n\n  /**\n   * Get current state\n   */\n  getState(): DuckBrainServiceState {\n    return { ...this.state };\n  }\n\n  /**\n   * Handle model initialization progress\n   */\n  private handleProgress = (progress: InitProgressReport) => {\n    const percent = Math.round(progress.progress * 100);\n    const isDownloading = progress.text.toLowerCase().includes(\"download\");\n\n    this.updateState({\n      status: isDownloading ? \"downloading\" : \"loading\",\n      downloadProgress: percent,\n      downloadStatus: progress.text,\n    });\n  };\n\n  /**\n   * Initialize the WebLLM engine with a specific model\n   */\n  async initialize(modelId: string = DEFAULT_MODEL.id): Promise<void> {\n    // Check WebGPU first\n    if (this.state.isWebGPUSupported === null) {\n      const supported = await this.checkWebGPUSupport();\n      if (!supported) {\n        throw new Error(this.state.error || \"WebGPU not supported\");\n      }\n    } else if (!this.state.isWebGPUSupported) {\n      throw new Error(this.state.error || \"WebGPU not supported\");\n    }\n\n    // Already loaded with this model\n    if (this.state.currentModel === modelId && this.engine) {\n      return;\n    }\n\n    try {\n      this.updateState({\n        status: \"loading\",\n        error: null,\n        downloadProgress: 0,\n        downloadStatus: \"Initializing...\",\n      });\n\n      // Cleanup existing engine\n      await this.cleanup();\n\n      // Create Web Worker for off-main-thread processing\n      this.worker = new Worker(new URL(\"./webllm.worker.ts\", import.meta.url), { type: \"module\" });\n\n      // Create engine with worker\n      this.engine = await CreateWebWorkerMLCEngine(this.worker, modelId, {\n        initProgressCallback: this.handleProgress,\n      });\n\n      this.updateState({\n        status: \"ready\",\n        currentModel: modelId,\n        downloadProgress: 100,\n        downloadStatus: \"Model ready\",\n        error: null,\n      });\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : \"Failed to initialize model\";\n\n      this.updateState({\n        status: \"error\",\n        error: errorMessage,\n        downloadProgress: 0,\n        downloadStatus: \"\",\n      });\n\n      throw error;\n    }\n  }\n\n  /**\n   * Generate text with streaming\n   */\n  async generateStreaming(\n    messages: ChatCompletionMessageParam[],\n    callbacks: StreamCallbacks,\n    options?: { maxTokens?: number; temperature?: number }\n  ): Promise<void> {\n    if (!this.engine || this.state.status !== \"ready\") {\n      throw new Error(\"Model not initialized. Please load a model first.\");\n    }\n\n    this.abortController = new AbortController();\n    let fullText = \"\";\n\n    try {\n      const stream = await this.engine.chat.completions.create({\n        messages,\n        stream: true,\n        max_tokens: options?.maxTokens ?? 512,\n        temperature: options?.temperature ?? 0.2,\n      });\n\n      for await (const chunk of stream) {\n        if (this.abortController?.signal.aborted) {\n          break;\n        }\n\n        const delta = chunk.choices[0]?.delta?.content || \"\";\n        fullText += delta;\n        callbacks.onToken(delta, fullText);\n      }\n\n      if (!this.abortController?.signal.aborted) {\n        callbacks.onComplete(fullText);\n      }\n    } catch (error) {\n      if (error instanceof Error && error.name !== \"AbortError\") {\n        callbacks.onError(error);\n      }\n    } finally {\n      this.abortController = null;\n    }\n  }\n\n  /**\n   * Generate text without streaming (for simpler use cases)\n   */\n  async generate(\n    messages: ChatCompletionMessageParam[],\n    options?: { maxTokens?: number; temperature?: number }\n  ): Promise<string> {\n    if (!this.engine || this.state.status !== \"ready\") {\n      throw new Error(\"Model not initialized. Please load a model first.\");\n    }\n\n    const response = await this.engine.chat.completions.create({\n      messages,\n      max_tokens: options?.maxTokens ?? 512,\n      temperature: options?.temperature ?? 0.2,\n    });\n\n    return response.choices[0]?.message?.content || \"\";\n  }\n\n  /**\n   * Abort current generation\n   */\n  abort(): void {\n    this.abortController?.abort();\n    this.abortController = null;\n  }\n\n  /**\n   * Cleanup resources\n   */\n  async cleanup(): Promise<void> {\n    this.abort();\n\n    if (this.engine) {\n      try {\n        await this.engine.unload();\n      } catch {\n        // Ignore cleanup errors\n      }\n      this.engine = null;\n    }\n\n    if (this.worker) {\n      this.worker.terminate();\n      this.worker = null;\n    }\n\n    this.updateState({\n      status: \"idle\",\n      currentModel: null,\n      downloadProgress: 0,\n      downloadStatus: \"\",\n    });\n  }\n\n  /**\n   * Check if engine is ready\n   */\n  isReady(): boolean {\n    return this.state.status === \"ready\" && this.engine !== null;\n  }\n}\n\n// Export singleton instance\nexport const duckBrainService = new DuckBrainService();\n"
  },
  {
    "path": "src/lib/duckBrain/webllm.worker.ts",
    "content": "import { WebWorkerMLCEngineHandler } from \"@mlc-ai/web-llm\";\n\n// Create handler for WebLLM engine in Web Worker\nconst handler = new WebWorkerMLCEngineHandler();\n\nself.onmessage = (msg: MessageEvent) => {\n  handler.onmessage(msg);\n};\n"
  },
  {
    "path": "src/lib/fileSystem/index.ts",
    "content": "/**\n * File System Access API Service\n * Provides persistent folder access across browser sessions\n */\nimport { generateUUID } from \"@/lib/utils\";\n\n// Types for file system entries\nexport interface FileEntry {\n  name: string;\n  path: string;\n  type: \"file\";\n  size: number;\n  lastModified: Date;\n  extension: string;\n  handle: FileSystemFileHandle;\n}\n\nexport interface FolderEntry {\n  name: string;\n  path: string;\n  type: \"folder\";\n  children?: (FileEntry | FolderEntry)[];\n  handle: FileSystemDirectoryHandle;\n}\n\nexport type FSEntry = FileEntry | FolderEntry;\n\nexport interface MountedFolder {\n  id: string;\n  name: string;\n  handle: FileSystemDirectoryHandle;\n  addedAt: Date;\n  hasPermission: boolean;\n}\n\n// Supported file extensions for DuckDB\nexport const SUPPORTED_EXTENSIONS = [\n  \".csv\",\n  \".tsv\",\n  \".json\",\n  \".jsonl\",\n  \".ndjson\",\n  \".parquet\",\n  \".arrow\",\n  \".ipc\",\n  \".duckdb\",\n  \".db\",\n  \".ddb\",\n  \".xlsx\",\n  \".xls\",\n];\n\n// Check if File System Access API is supported\nexport function isFileSystemAccessSupported(): boolean {\n  return \"showDirectoryPicker\" in window;\n}\n\n// IndexedDB database name and store\nconst DB_NAME = \"duck-ui-filesystem\";\nconst STORE_NAME = \"folder-handles\";\nconst DB_VERSION = 1;\n\n/**\n * Open IndexedDB for storing folder handles\n */\nasync function openDatabase(): Promise<IDBDatabase> {\n  return new Promise((resolve, reject) => {\n    const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve(request.result);\n\n    request.onupgradeneeded = (event) => {\n      const db = (event.target as IDBOpenDBRequest).result;\n      if (!db.objectStoreNames.contains(STORE_NAME)) {\n        db.createObjectStore(STORE_NAME, { keyPath: \"id\" });\n      }\n    };\n  });\n}\n\n/**\n * Verify read permission for a directory handle\n */\nexport async function verifyPermission(\n  handle: FileSystemDirectoryHandle,\n  mode: \"read\" | \"readwrite\" = \"read\"\n): Promise<boolean> {\n  const options: FileSystemHandlePermissionDescriptor = { mode };\n\n  // Check if we already have permission\n  if ((await handle.queryPermission(options)) === \"granted\") {\n    return true;\n  }\n\n  // Request permission\n  if ((await handle.requestPermission(options)) === \"granted\") {\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Get file extension\n */\nfunction getExtension(filename: string): string {\n  const lastDot = filename.lastIndexOf(\".\");\n  return lastDot > 0 ? filename.slice(lastDot).toLowerCase() : \"\";\n}\n\n/**\n * File System Service - Singleton\n */\nclass FileSystemService {\n  private db: IDBDatabase | null = null;\n  private folders: Map<string, MountedFolder> = new Map();\n  private initialized = false;\n\n  /**\n   * Initialize the service and load persisted folder handles\n   */\n  async init(): Promise<void> {\n    if (this.initialized) return;\n\n    if (!isFileSystemAccessSupported()) {\n      console.warn(\"File System Access API not supported\");\n      this.initialized = true;\n      return;\n    }\n\n    try {\n      this.db = await openDatabase();\n      await this.loadPersistedFolders();\n      this.initialized = true;\n    } catch (error) {\n      console.error(\"Failed to initialize FileSystemService:\", error);\n      throw error;\n    }\n  }\n\n  /**\n   * Load folder handles from IndexedDB\n   */\n  private async loadPersistedFolders(): Promise<void> {\n    if (!this.db) return;\n\n    return new Promise((resolve, reject) => {\n      const transaction = this.db!.transaction(STORE_NAME, \"readonly\");\n      const store = transaction.objectStore(STORE_NAME);\n      const request = store.getAll();\n\n      request.onerror = () => reject(request.error);\n      request.onsuccess = () => {\n        const folders = request.result as MountedFolder[];\n        for (const folder of folders) {\n          // Restore date objects\n          folder.addedAt = new Date(folder.addedAt);\n          folder.hasPermission = false; // Will verify on demand\n          this.folders.set(folder.id, folder);\n        }\n        resolve();\n      };\n    });\n  }\n\n  /**\n   * Save folder handle to IndexedDB\n   */\n  private async persistFolder(folder: MountedFolder): Promise<void> {\n    if (!this.db) return;\n\n    return new Promise((resolve, reject) => {\n      const transaction = this.db!.transaction(STORE_NAME, \"readwrite\");\n      const store = transaction.objectStore(STORE_NAME);\n      const request = store.put(folder);\n\n      request.onerror = () => reject(request.error);\n      request.onsuccess = () => resolve();\n    });\n  }\n\n  /**\n   * Remove folder handle from IndexedDB\n   */\n  private async removePersistedFolder(id: string): Promise<void> {\n    if (!this.db) return;\n\n    return new Promise((resolve, reject) => {\n      const transaction = this.db!.transaction(STORE_NAME, \"readwrite\");\n      const store = transaction.objectStore(STORE_NAME);\n      const request = store.delete(id);\n\n      request.onerror = () => reject(request.error);\n      request.onsuccess = () => resolve();\n    });\n  }\n\n  /**\n   * Show folder picker and mount the selected folder\n   */\n  async mountFolder(): Promise<MountedFolder> {\n    if (!isFileSystemAccessSupported()) {\n      throw new Error(\"File System Access API not supported in this browser\");\n    }\n\n    // Show the directory picker\n    const handle = await window.showDirectoryPicker({\n      mode: \"read\",\n    });\n\n    // Generate unique ID\n    const id = generateUUID();\n\n    const folder: MountedFolder = {\n      id,\n      name: handle.name,\n      handle,\n      addedAt: new Date(),\n      hasPermission: true,\n    };\n\n    // Store in memory and IndexedDB\n    this.folders.set(id, folder);\n    await this.persistFolder(folder);\n\n    return folder;\n  }\n\n  /**\n   * Unmount a folder\n   */\n  async unmountFolder(id: string): Promise<void> {\n    this.folders.delete(id);\n    await this.removePersistedFolder(id);\n  }\n\n  /**\n   * Get all mounted folders\n   */\n  getMountedFolders(): MountedFolder[] {\n    return Array.from(this.folders.values());\n  }\n\n  /**\n   * Get a specific folder by ID\n   */\n  getFolder(id: string): MountedFolder | undefined {\n    return this.folders.get(id);\n  }\n\n  /**\n   * Verify and request permission for a folder\n   */\n  async requestPermission(id: string): Promise<boolean> {\n    const folder = this.folders.get(id);\n    if (!folder) return false;\n\n    const hasPermission = await verifyPermission(folder.handle);\n    folder.hasPermission = hasPermission;\n    return hasPermission;\n  }\n\n  /**\n   * Check permission status for all folders\n   */\n  async checkAllPermissions(): Promise<Map<string, boolean>> {\n    const results = new Map<string, boolean>();\n\n    for (const [id, folder] of this.folders) {\n      try {\n        // Just query, don't request\n        const status = await folder.handle.queryPermission({ mode: \"read\" });\n        folder.hasPermission = status === \"granted\";\n        results.set(id, folder.hasPermission);\n      } catch {\n        folder.hasPermission = false;\n        results.set(id, false);\n      }\n    }\n\n    return results;\n  }\n\n  /**\n   * List files in a folder (with optional filtering)\n   */\n  async listFiles(\n    id: string,\n    options: {\n      recursive?: boolean;\n      filterSupported?: boolean;\n    } = {}\n  ): Promise<FSEntry[]> {\n    const { recursive = false, filterSupported = true } = options;\n    const folder = this.folders.get(id);\n\n    if (!folder) {\n      throw new Error(`Folder not found: ${id}`);\n    }\n\n    if (!folder.hasPermission) {\n      const granted = await this.requestPermission(id);\n      if (!granted) {\n        throw new Error(\"Permission denied\");\n      }\n    }\n\n    return this.readDirectory(folder.handle, \"\", recursive, filterSupported);\n  }\n\n  /**\n   * Read directory contents recursively\n   */\n  private async readDirectory(\n    handle: FileSystemDirectoryHandle,\n    basePath: string,\n    recursive: boolean,\n    filterSupported: boolean\n  ): Promise<FSEntry[]> {\n    const entries: FSEntry[] = [];\n\n    for await (const entry of handle.values()) {\n      const path = basePath ? `${basePath}/${entry.name}` : entry.name;\n\n      if (entry.kind === \"file\") {\n        const fileHandle = entry as FileSystemFileHandle;\n        const ext = getExtension(entry.name);\n\n        // Skip unsupported files if filtering\n        if (filterSupported && !SUPPORTED_EXTENSIONS.includes(ext)) {\n          continue;\n        }\n\n        try {\n          const file = await fileHandle.getFile();\n          entries.push({\n            name: entry.name,\n            path,\n            type: \"file\",\n            size: file.size,\n            lastModified: new Date(file.lastModified),\n            extension: ext,\n            handle: fileHandle,\n          });\n        } catch {\n          // Skip files we can't read\n        }\n      } else if (entry.kind === \"directory\") {\n        const dirHandle = entry as FileSystemDirectoryHandle;\n        const folderEntry: FolderEntry = {\n          name: entry.name,\n          path,\n          type: \"folder\",\n          handle: dirHandle,\n        };\n\n        if (recursive) {\n          folderEntry.children = await this.readDirectory(\n            dirHandle,\n            path,\n            recursive,\n            filterSupported\n          );\n        }\n\n        entries.push(folderEntry);\n      }\n    }\n\n    // Sort: folders first, then files, alphabetically\n    return entries.sort((a, b) => {\n      if (a.type !== b.type) {\n        return a.type === \"folder\" ? -1 : 1;\n      }\n      return a.name.localeCompare(b.name);\n    });\n  }\n\n  /**\n   * Read a specific file from a mounted folder\n   */\n  async readFile(folderId: string, filePath: string): Promise<File> {\n    const folder = this.folders.get(folderId);\n\n    if (!folder) {\n      throw new Error(`Folder not found: ${folderId}`);\n    }\n\n    if (!folder.hasPermission) {\n      const granted = await this.requestPermission(folderId);\n      if (!granted) {\n        throw new Error(\"Permission denied\");\n      }\n    }\n\n    // Navigate to the file\n    const parts = filePath.split(\"/\");\n    let currentHandle: FileSystemDirectoryHandle = folder.handle;\n\n    // Navigate through directories\n    for (let i = 0; i < parts.length - 1; i++) {\n      currentHandle = await currentHandle.getDirectoryHandle(parts[i]);\n    }\n\n    // Get the file\n    const fileName = parts[parts.length - 1];\n    const fileHandle = await currentHandle.getFileHandle(fileName);\n    return fileHandle.getFile();\n  }\n\n  /**\n   * Get file as ArrayBuffer\n   */\n  async readFileBuffer(folderId: string, filePath: string): Promise<ArrayBuffer> {\n    const file = await this.readFile(folderId, filePath);\n    return file.arrayBuffer();\n  }\n\n  /**\n   * Request write permission for a folder\n   */\n  async requestWritePermission(id: string): Promise<boolean> {\n    const folder = this.folders.get(id);\n    if (!folder) return false;\n\n    const hasPermission = await verifyPermission(folder.handle, \"readwrite\");\n    folder.hasPermission = hasPermission;\n    return hasPermission;\n  }\n\n  /**\n   * Save a file to a mounted folder\n   */\n  async saveFile(\n    folderId: string,\n    fileName: string,\n    content: Blob | ArrayBuffer | string,\n    subPath?: string\n  ): Promise<void> {\n    const folder = this.folders.get(folderId);\n\n    if (!folder) {\n      throw new Error(`Folder not found: ${folderId}`);\n    }\n\n    // Request write permission\n    const hasPermission = await this.requestWritePermission(folderId);\n    if (!hasPermission) {\n      throw new Error(\"Write permission denied\");\n    }\n\n    // Navigate to subfolder if specified\n    let targetHandle: FileSystemDirectoryHandle = folder.handle;\n    if (subPath) {\n      const parts = subPath.split(\"/\").filter(Boolean);\n      for (const part of parts) {\n        targetHandle = await targetHandle.getDirectoryHandle(part, { create: true });\n      }\n    }\n\n    // Create or overwrite the file\n    const fileHandle = await targetHandle.getFileHandle(fileName, { create: true });\n    const writable = await fileHandle.createWritable();\n\n    try {\n      if (typeof content === \"string\") {\n        await writable.write(content);\n      } else if (content instanceof Blob) {\n        await writable.write(content);\n      } else {\n        // ArrayBuffer\n        await writable.write(new Blob([content]));\n      }\n    } finally {\n      await writable.close();\n    }\n  }\n\n  /**\n   * Get folder handle for a mounted folder (for direct access)\n   */\n  getFolderHandle(id: string): FileSystemDirectoryHandle | undefined {\n    return this.folders.get(id)?.handle;\n  }\n}\n\n// Export singleton instance\nexport const fileSystemService = new FileSystemService();\n"
  },
  {
    "path": "src/lib/sqlSanitize.ts",
    "content": "/** Escape a string value for safe use in SQL single-quoted literals */\nexport function sqlEscapeString(value: string): string {\n  return value.replace(/'/g, \"''\");\n}\n\n/** Escape and double-quote an identifier (table name, column name, database name, etc.) */\nexport function sqlEscapeIdentifier(name: string): string {\n  return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n"
  },
  {
    "path": "src/lib/utils.ts",
    "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\n/**\n * Generate a UUID v4 string. Uses crypto.randomUUID() when available (secure\n * contexts / HTTPS), falls back to a crypto.getRandomValues()-based polyfill\n * for HTTP environments (e.g. Docker without TLS).\n * See: https://github.com/caioricciuti/duck-ui/issues/21\n */\nexport function generateUUID(): string {\n  if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n    return crypto.randomUUID();\n  }\n  // Fallback using crypto.getRandomValues\n  return \"10000000-1000-4000-8000-100000000000\".replace(/[018]/g, (c) => {\n    const n = Number(c);\n    return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);\n  });\n}\n\nexport const genTabId = () => {\n  const timestamp = Date.now().toString();\n  const randomStr = Math.random().toString(36).substring(2);\n  return timestamp + randomStr.slice(0, 26 - timestamp.length);\n};\n\nexport const formatBytes = (bytes: number, decimals = 2) => {\n  if (bytes === 0) return \"0 Bytes\";\n  const k = 1024;\n  const dm = decimals < 0 ? 0 : decimals;\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"];\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n};\n\nexport const formatDuration = (milliseconds: number) => {\n  const seconds = Math.floor((milliseconds / 1000) % 60);\n  const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);\n  const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);\n  const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));\n\n  const parts = [];\n  if (days > 0) parts.push(`${days}d`);\n  if (hours > 0) parts.push(`${hours}h`);\n  if (minutes > 0) parts.push(`${minutes}m`);\n  if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);\n\n  return parts.join(\" \");\n};\n"
  },
  {
    "path": "src/main.tsx",
    "content": "import \"./index.css\";\nimport { StrictMode, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { BrowserRouter } from \"react-router\";\nimport { Suspense } from \"react\";\nimport { ErrorBoundary, FallbackProps } from \"react-error-boundary\";\nimport { ThemeProvider } from \"@/components/theme/theme-provider\";\nimport { Routes, Route } from \"react-router\";\nimport { useDuckStore, startAutoSave } from \"./store\";\nimport Home from \"@/pages/Home\";\nimport { Toaster } from \"@/components/ui/sonner\";\nimport { Loader2, AlertTriangle, RefreshCw } from \"lucide-react\";\nimport { Navigate } from \"react-router\";\nimport { initializeSystemDb } from \"@/services/persistence/systemDb\";\nimport { listProfiles } from \"@/services/persistence/repositories/profileRepository\";\nimport ProfilePicker from \"@/components/profile/ProfilePicker\";\nimport type { Profile } from \"@/store/types\";\n\n// Import httpfs test utility for console access (window.testHttpfs) — dev only\nif (import.meta.env.DEV) {\n  import(\"@/lib/cloudStorage/testHttpfs\");\n}\n\ninterface LoadingScreenProps {\n  message: string;\n}\n\ninterface AppInitializerProps {\n  children: React.ReactNode;\n}\n\nconst LoadingScreen = ({ message }: LoadingScreenProps) => (\n  <div className=\"h-screen flex items-center justify-center bg-black/90 text-white\">\n    <div className=\"text-center\">\n      <Loader2 className=\"animate-spin m-auto mb-12\" size={64} />\n      <p className=\"text-lg\">{message}</p>\n    </div>\n  </div>\n);\n\nconst ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (\n  <div className=\"h-screen flex items-center justify-center bg-background text-foreground\">\n    <div className=\"text-center max-w-md p-6\">\n      <AlertTriangle className=\"mx-auto mb-4 text-destructive\" size={48} />\n      <h2 className=\"text-xl font-semibold mb-2\">Something went wrong</h2>\n      <p className=\"text-muted-foreground mb-4 text-sm\">\n        {error instanceof Error\n          ? error.message\n          : \"An unexpected error occurred while rendering the application.\"}\n      </p>\n      <button\n        onClick={resetErrorBoundary}\n        className=\"inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n      >\n        <RefreshCw size={16} />\n        Try again\n      </button>\n    </div>\n  </div>\n);\n\n/**\n * ProfileBootstrap — initializes the system database and loads the user's profile\n * before the main app starts. Handles first-time setup and localStorage migration.\n * When multiple profiles exist, shows a profile picker screen.\n */\nconst ProfileBootstrap = ({ children }: { children: React.ReactNode }) => {\n  const [ready, setReady] = useState(false);\n  const [showPicker, setShowPicker] = useState(false);\n  const [bootProfiles, setBootProfiles] = useState<Profile[]>([]);\n  const createProfile = useDuckStore((s) => s.createProfile);\n  const loadProfile = useDuckStore((s) => s.loadProfile);\n  const bootedRef = useRef(false);\n\n  useEffect(() => {\n    if (bootedRef.current) return;\n    bootedRef.current = true;\n\n    async function boot() {\n      try {\n        await initializeSystemDb();\n        const profiles = await listProfiles();\n\n        if (profiles.length === 1 && !profiles[0].has_password) {\n          // Single unprotected profile: auto-load\n          await loadProfile(profiles[0].id);\n          startAutoSave();\n          setReady(true);\n        } else {\n          // 0 profiles, multiple profiles, or single password-protected: show picker\n          setBootProfiles(\n            profiles.map((p) => ({\n              id: p.id,\n              name: p.name,\n              avatarEmoji: p.avatar_emoji,\n              hasPassword: p.has_password,\n              createdAt: p.created_at,\n              lastActive: p.last_active,\n            }))\n          );\n          setShowPicker(true);\n        }\n      } catch (error) {\n        console.error(\"[ProfileBootstrap] Failed to initialize profile:\", error);\n        setReady(true);\n      }\n    }\n\n    boot();\n  }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n  if (!ready && !showPicker) {\n    return <LoadingScreen message=\"Loading profile...\" />;\n  }\n\n  if (showPicker && !ready) {\n    return (\n      <ThemeProvider defaultTheme=\"dark\" storageKey=\"vite-ui-theme\">\n        <ProfilePicker\n          profiles={bootProfiles}\n          onSelectProfile={async (id, password) => {\n            await loadProfile(id, password);\n            startAutoSave();\n            setReady(true);\n            setShowPicker(false);\n          }}\n          onCreateProfile={async (name, password, avatarEmoji) => {\n            const id = await createProfile(name, password, avatarEmoji);\n            await migrateFromLocalStorage(id);\n            await loadProfile(id, password);\n            startAutoSave();\n            setReady(true);\n            setShowPicker(false);\n            return id;\n          }}\n        />\n      </ThemeProvider>\n    );\n  }\n\n  return children;\n};\n\n/**\n * Migrate existing localStorage state to the system database.\n * One-time operation on first boot after the profile system is introduced.\n */\nasync function migrateFromLocalStorage(profileId: string): Promise<void> {\n  if (localStorage.getItem(\"duck-ui-migrated\") === \"true\") return;\n\n  const raw = localStorage.getItem(\"duck-ui-storage\");\n  if (!raw) {\n    localStorage.setItem(\"duck-ui-migrated\", \"true\");\n    return;\n  }\n\n  try {\n    const parsed = JSON.parse(raw);\n    const state = parsed.state ?? parsed;\n\n    const { saveWorkspace } =\n      await import(\"@/services/persistence/repositories/workspaceRepository\");\n    const { addHistoryEntry } =\n      await import(\"@/services/persistence/repositories/queryHistoryRepository\");\n    const { saveProviderConfig, saveConversation } =\n      await import(\"@/services/persistence/repositories/aiConfigRepository\");\n    const { setSetting } = await import(\"@/services/persistence/repositories/settingsRepository\");\n    const { saveConnection } =\n      await import(\"@/services/persistence/repositories/connectionRepository\");\n    const { loadKeyForProfile } = await import(\"@/services/persistence/crypto\");\n\n    // Load the encryption key for this profile\n    const keyData = await loadKeyForProfile(profileId);\n    const cryptoKey = keyData?.key ?? null;\n\n    // Migrate connections\n    const connections = state.connectionList?.connections ?? [];\n    for (const conn of connections) {\n      if (!conn.id || conn.scope === \"WASM\") continue;\n      const config: Record<string, unknown> = {\n        host: conn.host,\n        port: conn.port,\n        database: conn.database,\n        path: conn.path,\n        authMode: conn.authMode,\n      };\n      const credentials: Record<string, unknown> = {};\n      if (conn.password) credentials.password = conn.password;\n      if (conn.apiKey) credentials.apiKey = conn.apiKey;\n\n      await saveConnection(\n        profileId,\n        {\n          name: conn.name ?? \"Untitled\",\n          scope: conn.scope ?? \"External\",\n          config,\n          credentials: Object.keys(credentials).length > 0 ? credentials : undefined,\n          environment: conn.environment ?? \"APP\",\n        },\n        cryptoKey\n      );\n    }\n\n    // Migrate query history\n    for (const item of state.queryHistory ?? []) {\n      await addHistoryEntry(profileId, item.query, {\n        error: item.error,\n      });\n    }\n\n    // Migrate workspace state (tabs)\n    if (state.tabs) {\n      const tabsJson = JSON.stringify(\n        state.tabs.map((t: Record<string, unknown>) => ({ ...t, result: undefined }))\n      );\n      await saveWorkspace(profileId, {\n        tabs: tabsJson,\n        activeTabId: (state.activeTabId as string) ?? null,\n        currentConnectionId:\n          ((state.currentConnection as Record<string, unknown>)?.id as string) ?? null,\n        currentDatabase: (state.currentDatabase as string) ?? null,\n      });\n    }\n\n    // Migrate AI provider configs\n    const providerConfigs = state.duckBrain?.providerConfigs ?? {};\n    for (const [provider, config] of Object.entries(providerConfigs)) {\n      if (!config) continue;\n      const cfg = config as Record<string, string>;\n      const apiKey = cfg.apiKey ?? null;\n      const safeConfig: Record<string, unknown> = { modelId: cfg.modelId };\n      if (cfg.baseUrl) safeConfig.baseUrl = cfg.baseUrl;\n      await saveProviderConfig(profileId, provider, safeConfig, apiKey, cryptoKey);\n    }\n\n    // Migrate AI messages\n    if (state.duckBrain?.messages?.length) {\n      await saveConversation(profileId, state.duckBrain.messages, {\n        title: \"Migrated conversation\",\n        provider: state.duckBrain.aiProvider,\n      });\n    }\n\n    // Migrate theme\n    const theme = localStorage.getItem(\"vite-ui-theme\");\n    if (theme) {\n      await setSetting(profileId, \"theme\", \"mode\", JSON.stringify(theme));\n    }\n\n    console.info(\"[Migration] Successfully migrated localStorage data to system DB\");\n  } catch (error) {\n    console.warn(\"[Migration] Failed to migrate localStorage:\", error);\n  }\n\n  localStorage.setItem(\"duck-ui-migrated\", \"true\");\n}\n\nconst AppInitializer = ({ children }: AppInitializerProps) => {\n  const initialize = useDuckStore((s) => s.initialize);\n  const isInitialized = useDuckStore((s) => s.isInitialized);\n  const isLoading = useDuckStore((s) => s.isLoading);\n\n  useEffect(() => {\n    initialize();\n  }, [initialize]);\n\n  if (isLoading || !isInitialized) {\n    return <LoadingScreen message=\"Initializing DuckDB\" />;\n  }\n\n  return children;\n};\n\nconst App = () => {\n  useEffect(() => {\n    const handleBeforeUnload = (e: BeforeUnloadEvent) => {\n      // Only prompt when there are SQL tabs with unsaved content\n      const state = useDuckStore.getState();\n      const hasUnsavedWork = state.tabs.some(\n        (t) => t.type === \"sql\" && typeof t.content === \"string\" && t.content.trim().length > 0\n      );\n      if (hasUnsavedWork) {\n        e.preventDefault();\n      }\n    };\n\n    window.addEventListener(\"beforeunload\", handleBeforeUnload);\n    return () => window.removeEventListener(\"beforeunload\", handleBeforeUnload);\n  }, []);\n\n  return (\n    <div className=\"flex flex-col w-full h-screen overflow-hidden\">\n      <Toaster richColors toastOptions={{ duration: 2000, closeButton: true }} expand={true} />\n      <div className=\"flex-1 overflow-hidden\">\n        <Routes>\n          <Route path=\"/\" element={<Home />} />\n          <Route path=\"*\" element={<Navigate to=\"/\" replace />} />\n        </Routes>\n      </div>\n    </div>\n  );\n};\n\nconst rootElement = document.getElementById(\"root\");\nif (!rootElement) throw new Error(\"Failed to find root element\");\n\n// Production render\ncreateRoot(rootElement).render(\n  <StrictMode>\n    <ErrorBoundary FallbackComponent={ErrorFallback}>\n      <ProfileBootstrap>\n        <AppInitializer>\n          <BrowserRouter basename={import.meta.env.BASE_URL}>\n            <ThemeProvider defaultTheme=\"dark\" storageKey=\"vite-ui-theme\">\n              <Suspense fallback={<LoadingScreen message=\"Loading application\" />}>\n                <App />\n              </Suspense>\n            </ThemeProvider>\n          </BrowserRouter>\n        </AppInitializer>\n      </ProfileBootstrap>\n    </ErrorBoundary>\n  </StrictMode>\n);\n"
  },
  {
    "path": "src/pages/Home.tsx",
    "content": "import { useState } from \"react\";\nimport { Menu } from \"lucide-react\";\nimport DataExplorer from \"@/components/explorer/DataExplorer\";\nimport Sidebar from \"@/components/layout/Sidebar\";\nimport { ResizableHandle, ResizablePanel, ResizablePanelGroup } from \"@/components/ui/resizable\";\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from \"@/components/ui/sheet\";\nimport { Button } from \"@/components/ui/button\";\nimport WorkspaceTabs from \"@/components/workspace/WorkspaceTabs\";\nimport CommandPalette from \"@/components/command-palette/CommandPalette\";\n\nexport default function Home() {\n  const [dataExplorerOpen, setDataExplorerOpen] = useState(false);\n  const [isExplorerVisible, setIsExplorerVisible] = useState(true);\n\n  return (\n    <div className=\"h-screen w-full flex overflow-hidden\">\n      <CommandPalette />\n      {/* Sidebar - Desktop only */}\n      <aside className=\"hidden md:flex\">\n        <Sidebar\n          isExplorerOpen={isExplorerVisible}\n          onToggleExplorer={() => setIsExplorerVisible(!isExplorerVisible)}\n        />\n      </aside>\n\n      {/* Main Content */}\n      <main className=\"flex-1 flex flex-col overflow-hidden\">\n        {/* Desktop Layout - ResizablePanels */}\n        <div className=\"hidden md:flex h-full\">\n          <ResizablePanelGroup direction=\"horizontal\">\n            {isExplorerVisible && (\n              <>\n                <ResizablePanel\n                  className=\"overflow-auto\"\n                  defaultSize={20}\n                  minSize={15}\n                  maxSize={35}\n                >\n                  <DataExplorer />\n                </ResizablePanel>\n                <ResizableHandle withHandle />\n              </>\n            )}\n            <ResizablePanel\n              className=\"overflow-auto\"\n              defaultSize={isExplorerVisible ? 80 : 100}\n              minSize={50}\n            >\n              <WorkspaceTabs />\n            </ResizablePanel>\n          </ResizablePanelGroup>\n        </div>\n\n        {/* Mobile Layout - Stacked with Drawer */}\n        <div className=\"md:hidden h-full flex flex-col\">\n          {/* Data Explorer Drawer */}\n          <Sheet open={dataExplorerOpen} onOpenChange={setDataExplorerOpen}>\n            <SheetTrigger asChild>\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"absolute top-16 left-2 z-40 flex items-center gap-2\"\n              >\n                <Menu className=\"h-4 w-4\" />\n                <span className=\"text-xs\">Tables</span>\n              </Button>\n            </SheetTrigger>\n            <SheetContent side=\"left\" className=\"w-[300px] sm:w-[350px] p-0\">\n              <SheetHeader className=\"px-4 py-3 border-b\">\n                <SheetTitle>Data Explorer</SheetTitle>\n              </SheetHeader>\n              <div className=\"h-[calc(100%-60px)] overflow-auto\">\n                <DataExplorer />\n              </div>\n            </SheetContent>\n          </Sheet>\n\n          {/* Main Workspace - Full Screen */}\n          <div className=\"flex-1 overflow-auto\">\n            <WorkspaceTabs />\n          </div>\n        </div>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/services/duckdb/__tests__/resultParser.test.ts",
    "content": "import { describe, it, expect } from \"vitest\";\nimport { rawResultToJSON } from \"../resultParser\";\n\ndescribe(\"rawResultToJSON\", () => {\n  it(\"returns empty result for empty string\", () => {\n    const result = rawResultToJSON(\"\");\n    expect(result).toEqual({ columns: [], columnTypes: [], data: [], rowCount: 0 });\n  });\n\n  it(\"returns empty result for whitespace-only string\", () => {\n    const result = rawResultToJSON(\"   \\n  \");\n    expect(result).toEqual({ columns: [], columnTypes: [], data: [], rowCount: 0 });\n  });\n\n  describe(\"JSONCompact format\", () => {\n    it(\"parses standard JSONCompact response\", () => {\n      const input = JSON.stringify({\n        meta: [\n          { name: \"id\", type: \"INTEGER\" },\n          { name: \"name\", type: \"VARCHAR\" },\n        ],\n        data: [\n          [1, \"Alice\"],\n          [2, \"Bob\"],\n        ],\n        rows: 2,\n      });\n\n      const result = rawResultToJSON(input);\n      expect(result.columns).toEqual([\"id\", \"name\"]);\n      expect(result.columnTypes).toEqual([\"INTEGER\", \"VARCHAR\"]);\n      expect(result.data).toEqual([\n        { id: 1, name: \"Alice\" },\n        { id: 2, name: \"Bob\" },\n      ]);\n      expect(result.rowCount).toBe(2);\n    });\n\n    it(\"uses data.length when rows field is missing\", () => {\n      const input = JSON.stringify({\n        meta: [{ name: \"col\", type: \"INTEGER\" }],\n        data: [[1], [2], [3]],\n      });\n\n      const result = rawResultToJSON(input);\n      expect(result.rowCount).toBe(3);\n    });\n\n    it(\"handles empty data array\", () => {\n      const input = JSON.stringify({\n        meta: [{ name: \"col\", type: \"INTEGER\" }],\n        data: [],\n        rows: 0,\n      });\n\n      const result = rawResultToJSON(input);\n      expect(result.columns).toEqual([\"col\"]);\n      expect(result.data).toEqual([]);\n      expect(result.rowCount).toBe(0);\n    });\n  });\n\n  describe(\"array-of-objects format\", () => {\n    it(\"parses array of objects\", () => {\n      const input = JSON.stringify([\n        { col1: \"val1\", col2: 42 },\n        { col1: \"val2\", col2: 99 },\n      ]);\n\n      const result = rawResultToJSON(input);\n      expect(result.columns).toEqual([\"col1\", \"col2\"]);\n      expect(result.columnTypes).toEqual([\"VARCHAR\", \"VARCHAR\"]);\n      expect(result.data).toHaveLength(2);\n      expect(result.data[0]).toEqual({ col1: \"val1\", col2: 42 });\n      expect(result.rowCount).toBe(2);\n    });\n\n    it(\"handles single object array\", () => {\n      const input = JSON.stringify([{ x: 1 }]);\n      const result = rawResultToJSON(input);\n      expect(result.columns).toEqual([\"x\"]);\n      expect(result.data).toEqual([{ x: 1 }]);\n    });\n  });\n\n  describe(\"NDJSON format\", () => {\n    it(\"parses NDJSON with plain objects\", () => {\n      const input = '{\"a\": 1, \"b\": \"hello\"}\\n{\"a\": 2, \"b\": \"world\"}';\n\n      const result = rawResultToJSON(input);\n      expect(result.columns).toEqual([\"a\", \"b\"]);\n      expect(result.data).toEqual([\n        { a: 1, b: \"hello\" },\n        { a: 2, b: \"world\" },\n      ]);\n      expect(result.rowCount).toBe(2);\n    });\n\n    it(\"parses NDJSON with meta+data result object\", () => {\n      const input =\n        '{\"status\": \"ok\"}\\n' +\n        JSON.stringify({\n          meta: [{ name: \"id\", type: \"INT\" }],\n          data: [[1], [2]],\n          rows: 2,\n        });\n\n      const result = rawResultToJSON(input);\n      expect(result.columns).toEqual([\"id\"]);\n      expect(result.data).toEqual([{ id: 1 }, { id: 2 }]);\n    });\n\n    it(\"handles blank lines in NDJSON\", () => {\n      const input = '{\"a\": 1}\\n\\n{\"a\": 2}\\n';\n      const result = rawResultToJSON(input);\n      expect(result.data).toHaveLength(2);\n    });\n  });\n\n  describe(\"error handling\", () => {\n    it(\"throws on invalid JSON\", () => {\n      expect(() => rawResultToJSON(\"not json at all\")).toThrow(\"Failed to parse query result\");\n    });\n\n    it(\"throws when meta is missing from JSONCompact\", () => {\n      const input = JSON.stringify({ data: [[1]] });\n      expect(() => rawResultToJSON(input)).toThrow(\"Failed to parse query result\");\n    });\n\n    it(\"throws when data is missing from JSONCompact\", () => {\n      const input = JSON.stringify({\n        meta: [{ name: \"col\", type: \"INT\" }],\n      });\n      expect(() => rawResultToJSON(input)).toThrow(\"Failed to parse query result\");\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/duckdb/__tests__/utils.test.ts",
    "content": "import { describe, it, expect, vi } from \"vitest\";\nimport { retryWithBackoff, updateHistory } from \"../utils\";\n\n// Mock generateUUID since it depends on crypto\nvi.mock(\"@/lib/utils\", () => ({\n  generateUUID: () => \"test-uuid-\" + Math.random().toString(36).slice(2, 8),\n}));\n\ndescribe(\"retryWithBackoff\", () => {\n  it(\"returns result on first success\", async () => {\n    const op = vi.fn().mockResolvedValue(\"ok\");\n    const result = await retryWithBackoff(op);\n    expect(result).toBe(\"ok\");\n    expect(op).toHaveBeenCalledTimes(1);\n  });\n\n  it(\"retries on failure and eventually succeeds\", async () => {\n    const op = vi\n      .fn()\n      .mockRejectedValueOnce(new Error(\"fail 1\"))\n      .mockRejectedValueOnce(new Error(\"fail 2\"))\n      .mockResolvedValue(\"ok\");\n\n    const result = await retryWithBackoff(op, 3, 10);\n    expect(result).toBe(\"ok\");\n    expect(op).toHaveBeenCalledTimes(3);\n  });\n\n  it(\"throws after all retries exhausted\", async () => {\n    const op = vi.fn().mockRejectedValue(new Error(\"always fail\"));\n    await expect(retryWithBackoff(op, 2, 10)).rejects.toThrow(\"always fail\");\n    expect(op).toHaveBeenCalledTimes(2);\n  });\n\n  it(\"uses exponential backoff delays\", async () => {\n    const delays: number[] = [];\n    const originalSetTimeout = globalThis.setTimeout;\n\n    vi.spyOn(globalThis, \"setTimeout\").mockImplementation(((fn: () => void, delay: number) => {\n      delays.push(delay);\n      return originalSetTimeout(fn, 0);\n    }) as typeof setTimeout);\n\n    const op = vi\n      .fn()\n      .mockRejectedValueOnce(new Error(\"fail\"))\n      .mockRejectedValueOnce(new Error(\"fail\"))\n      .mockResolvedValue(\"ok\");\n\n    await retryWithBackoff(op, 3, 100);\n    expect(delays).toEqual([100, 200]);\n\n    vi.restoreAllMocks();\n  });\n});\n\ndescribe(\"updateHistory\", () => {\n  it(\"adds a new query to empty history\", () => {\n    const result = updateHistory([], \"SELECT 1\");\n    expect(result).toHaveLength(1);\n    expect(result[0].query).toBe(\"SELECT 1\");\n    expect(result[0].id).toBeTruthy();\n    expect(result[0].timestamp).toBeInstanceOf(Date);\n    expect(result[0].error).toBeUndefined();\n  });\n\n  it(\"adds error message when provided\", () => {\n    const result = updateHistory([], \"BAD SQL\", \"syntax error\");\n    expect(result[0].error).toBe(\"syntax error\");\n  });\n\n  it(\"moves duplicate query to the top\", () => {\n    const existing = [\n      { id: \"1\", query: \"SELECT 1\", timestamp: new Date() },\n      { id: \"2\", query: \"SELECT 2\", timestamp: new Date() },\n      { id: \"3\", query: \"SELECT 3\", timestamp: new Date() },\n    ];\n\n    const result = updateHistory(existing, \"SELECT 2\");\n    expect(result).toHaveLength(3);\n    expect(result[0].query).toBe(\"SELECT 2\");\n    expect(result[1].query).toBe(\"SELECT 1\");\n    expect(result[2].query).toBe(\"SELECT 3\");\n  });\n\n  it(\"caps history at 15 items\", () => {\n    const existing = Array.from({ length: 15 }, (_, i) => ({\n      id: String(i),\n      query: `SELECT ${i}`,\n      timestamp: new Date(),\n    }));\n\n    const result = updateHistory(existing, \"SELECT new\");\n    expect(result).toHaveLength(15);\n    expect(result[0].query).toBe(\"SELECT new\");\n  });\n\n  it(\"prepends new queries to the front\", () => {\n    const existing = [{ id: \"1\", query: \"SELECT 1\", timestamp: new Date() }];\n    const result = updateHistory(existing, \"SELECT 2\");\n    expect(result[0].query).toBe(\"SELECT 2\");\n    expect(result[1].query).toBe(\"SELECT 1\");\n  });\n});\n"
  },
  {
    "path": "src/services/duckdb/externalConnection.ts",
    "content": "import { rawResultToJSON } from \"./resultParser\";\nimport { sqlEscapeIdentifier, sqlEscapeString } from \"@/lib/sqlSanitize\";\nimport type {\n  CurrentConnection,\n  ConnectionProvider,\n  QueryResult,\n  ColumnInfo,\n  TableInfo,\n  DatabaseInfo,\n} from \"@/store/types\";\n\n/**\n * Builds a properly formatted URL from a connection's host and port.\n * Handles scheme prefixing, port detection (ignoring colons in the scheme),\n * and trailing slash normalisation.\n */\nconst buildConnectionUrl = (host: string, port?: string | number): string => {\n  let url = host;\n  if (!url.startsWith(\"http://\") && !url.startsWith(\"https://\")) {\n    url = `https://${url}`;\n  }\n  // Only append port when the hostname portion doesn't already contain one.\n  // Strip the scheme first so the \":\" in \"https://\" isn't matched.\n  if (port) {\n    const hostWithoutScheme = url.replace(/^https?:\\/\\//, \"\");\n    if (!hostWithoutScheme.includes(\":\")) {\n      url = `${url}:${port}`;\n    }\n  }\n  if (!url.endsWith(\"/\")) {\n    url = `${url}/`;\n  }\n  return url;\n};\n\n/**\n * Executes a query against an external connection.\n */\nexport const executeExternalQuery = async (\n  query: string,\n  connection: CurrentConnection\n): Promise<QueryResult> => {\n  if (!connection.host) {\n    throw new Error(\"Host must be defined for external connections.\");\n  }\n\n  const url = buildConnectionUrl(connection.host, connection.port);\n\n  // Build headers based on auth mode\n  const headers: Record<string, string> = {\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n    format: \"JSONCompact\",\n  };\n\n  if (connection.authMode === \"api_key\" && connection.apiKey) {\n    headers[\"X-API-Key\"] = connection.apiKey;\n  } else if (connection.authMode === \"password\" && connection.user && connection.password) {\n    const authHeader = btoa(`${connection.user}:${connection.password}`);\n    headers[\"Authorization\"] = `Basic ${authHeader}`;\n  }\n\n  try {\n    const response = await fetch(url, {\n      method: \"POST\",\n      headers,\n      body: query,\n    });\n\n    if (!response.ok) {\n      const errorText = await response.text();\n      if (response.status === 401) {\n        throw new Error(\"Authentication failed - check your credentials\");\n      } else if (response.status === 404) {\n        throw new Error(`Cannot reach server at ${url}`);\n      }\n      throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorText}`);\n    }\n\n    const rawResult = await response.text();\n    return rawResultToJSON(rawResult);\n  } catch (error) {\n    if (error instanceof TypeError && error.message.includes(\"fetch\")) {\n      throw new Error(\n        `Network error: Cannot reach ${url}. Check your connection and CORS settings.`\n      );\n    }\n    throw error;\n  }\n};\n\n/**\n * Tests an external connection by executing a basic query.\n */\nexport const testExternalConnection = async (connection: ConnectionProvider): Promise<void> => {\n  if (!connection.host) {\n    throw new Error(\"Host must be defined for external connections.\");\n  }\n\n  const url = buildConnectionUrl(connection.host, connection.port);\n\n  // Build headers based on auth mode\n  const headers: Record<string, string> = {\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n  };\n\n  if (connection.authMode === \"api_key\" && connection.apiKey) {\n    headers[\"X-API-Key\"] = connection.apiKey;\n  } else if (connection.authMode === \"password\" && connection.user && connection.password) {\n    const authHeader = btoa(`${connection.user}:${connection.password}`);\n    headers[\"Authorization\"] = `Basic ${authHeader}`;\n  }\n\n  try {\n    const response = await fetch(url, {\n      method: \"POST\",\n      headers,\n      body: `SELECT 1`,\n    });\n\n    if (!response.ok) {\n      const errorText = await response.text();\n      if (response.status === 401) {\n        throw new Error(\"Authentication failed - check your credentials\");\n      } else if (response.status === 404) {\n        throw new Error(`Cannot reach server at ${url}`);\n      }\n      throw new Error(`Connection test failed! Status: ${response.status}, Message: ${errorText}`);\n    }\n  } catch (error) {\n    if (error instanceof TypeError && error.message.includes(\"fetch\")) {\n      throw new Error(\n        `Network error: Cannot reach ${url}. Check your connection and CORS settings.`\n      );\n    }\n    throw error;\n  }\n};\n\n/**\n * Fetches databases and tables for an external connection.\n */\nexport const fetchExternalDatabases = async (\n  connection: CurrentConnection\n): Promise<DatabaseInfo[]> => {\n  try {\n    // Try to get database list\n    const dbListResult = await executeExternalQuery(\"SHOW DATABASES\", connection);\n    const databases: DatabaseInfo[] = [];\n\n    // If database list is available, fetch tables for each\n    if (dbListResult.data && dbListResult.data.length > 0) {\n      for (const dbRow of dbListResult.data) {\n        const dbName = dbRow[dbListResult.columns[0] as string] as string;\n        try {\n          const tablesResult = await executeExternalQuery(\n            `SELECT table_name FROM information_schema.tables WHERE table_catalog = '${sqlEscapeString(dbName)}'`,\n            connection\n          );\n\n          const tables: TableInfo[] = [];\n          for (const tableRow of tablesResult.data) {\n            const tableName = tableRow.table_name as string;\n            try {\n              // Try to get columns info\n              const columnsResult = await executeExternalQuery(\n                `DESCRIBE ${sqlEscapeIdentifier(dbName)}.${sqlEscapeIdentifier(tableName)}`,\n                connection\n              );\n\n              const columns: ColumnInfo[] = columnsResult.data.map(\n                (col: Record<string, unknown>) => ({\n                  name: col.column_name as string,\n                  type: col.column_type as string,\n                  nullable: col.null === \"YES\",\n                })\n              );\n\n              tables.push({\n                name: tableName,\n                schema: dbName,\n                columns,\n                rowCount: 0, // External connections don't provide row count easily\n                createdAt: new Date().toISOString(),\n              });\n            } catch {\n              // If describe fails, add table with basic info\n              tables.push({\n                name: tableName,\n                schema: dbName,\n                columns: [],\n                rowCount: 0,\n                createdAt: new Date().toISOString(),\n              });\n            }\n          }\n\n          if (tables.length > 0 || dbListResult.data.length === 1) {\n            databases.push({ name: dbName, tables });\n          }\n        } catch (e) {\n          console.warn(`Failed to fetch tables for database ${dbName}:`, e);\n          databases.push({ name: dbName, tables: [] });\n        }\n      }\n    }\n\n    return databases;\n  } catch (error) {\n    // If fetching databases fails, return empty array\n    console.warn(\"Failed to fetch external databases:\", error);\n    return [];\n  }\n};\n"
  },
  {
    "path": "src/services/duckdb/index.ts",
    "content": "// DuckDB Service Layer\n// Extracted from the monolithic store for testability and modularity.\n\nexport { rawResultToJSON, resultToJSON } from \"./resultParser\";\nexport {\n  initializeWasmConnection,\n  loadEmbeddedDatabases,\n  resolveDuckdbBundles,\n} from \"./wasmConnection\";\nexport { cleanupOPFSConnection, testOPFSConnection, opfsActivePaths } from \"./opfsConnection\";\nexport {\n  executeExternalQuery,\n  testExternalConnection,\n  fetchExternalDatabases,\n} from \"./externalConnection\";\nexport { fetchWasmDatabases } from \"./schemaFetcher\";\nexport { retryWithBackoff, validateConnection, updateHistory } from \"./utils\";\n"
  },
  {
    "path": "src/services/duckdb/opfsConnection.ts",
    "content": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { retryWithBackoff, validateConnection } from \"./utils\";\nimport { createDuckdbWorker, resolveDuckdbBundles } from \"./wasmConnection\";\nimport type { ConnectionProvider } from \"@/store/types\";\n\n// OPFS connection tracking to prevent concurrent access\nexport const opfsActivePaths = new Set<string>();\n\n/** Normalize an OPFS path: strip leading slash, ensure .db extension. */\nfunction normalizeOpfsPath(path: string): string {\n  let normalized = path.startsWith(\"/\") ? path.slice(1) : path;\n  if (!normalized.endsWith(\".db\")) {\n    normalized = `${normalized}.db`;\n  }\n  return normalized;\n}\n\n/**\n * Centralized OPFS connection cleanup with proper handle release.\n */\nexport const cleanupOPFSConnection = async (\n  db: duckdb.AsyncDuckDB | null,\n  connection: duckdb.AsyncDuckDBConnection | null,\n  path?: string\n): Promise<void> => {\n  if (db) {\n    try {\n      if (connection) {\n        await connection.close();\n      }\n      await db.terminate();\n      // Critical: Wait for file handles to be fully released by browser\n      await new Promise((resolve) => setTimeout(resolve, 2000));\n      if (path) {\n        opfsActivePaths.delete(normalizeOpfsPath(path));\n      }\n    } catch (error) {\n      console.error(\"OPFS cleanup error:\", error);\n      // Still wait even if cleanup failed - handles may still release\n      await new Promise((resolve) => setTimeout(resolve, 2000));\n      if (path) {\n        opfsActivePaths.delete(normalizeOpfsPath(path));\n      }\n    }\n  }\n};\n\n/**\n * Tests an OPFS connection by executing a basic query.\n */\nexport const testOPFSConnection = async (\n  conn: ConnectionProvider\n): Promise<{\n  db: duckdb.AsyncDuckDB;\n  connection: duckdb.AsyncDuckDBConnection;\n}> => {\n  // Fail fast if cross-origin isolation is not active (SharedArrayBuffer unavailable)\n  if (!self.crossOriginIsolated) {\n    throw new Error(\n      \"OPFS requires cross-origin isolation (SharedArrayBuffer). \" +\n        \"The server must send headers: \" +\n        \"Cross-Origin-Opener-Policy: same-origin and \" +\n        \"Cross-Origin-Embedder-Policy: credentialless (or require-corp). \" +\n        \"Safari does not support credentialless — use require-corp instead.\"\n    );\n  }\n\n  const { path } = conn;\n  if (!path) {\n    throw new Error(\"Path must be defined for OPFS connections.\");\n  }\n\n  const opfsPath = normalizeOpfsPath(path);\n\n  // Check if path is already in use\n  if (opfsActivePaths.has(opfsPath)) {\n    throw new Error(\n      `OPFS file \"${opfsPath}\" is already open. Please close the existing connection first.`\n    );\n  }\n\n  // Reserve the path immediately to prevent concurrent access (TOCTOU)\n  opfsActivePaths.add(opfsPath);\n\n  // Timeout to prevent indefinite hangs when SharedArrayBuffer is unavailable\n  // (missing Cross-Origin-Opener-Policy / Cross-Origin-Embedder-Policy headers)\n  const TIMEOUT_MS = 30_000;\n\n  const connectionLogic = async (): Promise<{\n    db: duckdb.AsyncDuckDB;\n    connection: duckdb.AsyncDuckDBConnection;\n  }> => {\n    const bundles = await resolveDuckdbBundles();\n    const bundle = await duckdb.selectBundle(bundles);\n    if (!bundle.mainWorker) {\n      opfsActivePaths.delete(opfsPath);\n      throw new Error(\"DuckDB WASM bundle is missing the main worker URL\");\n    }\n    const { worker, revoke } = createDuckdbWorker(bundle.mainWorker);\n    const logger = new duckdb.VoidLogger();\n    const db = new duckdb.AsyncDuckDB(logger, worker);\n\n    try {\n      await db.instantiate(bundle.mainModule);\n    } finally {\n      revoke();\n    }\n\n    try {\n      // Use retry with exponential backoff for OPFS access handle conflicts\n      await retryWithBackoff(\n        async () => {\n          try {\n            await db.open({\n              path: `opfs://${opfsPath}`,\n              accessMode: duckdb.DuckDBAccessMode.AUTOMATIC,\n            });\n          } catch (error) {\n            const err = error as Error;\n            if (err.message.includes(\"createSyncAccessHandle\")) {\n              throw new Error(\n                `OPFS access handle conflict for \"${opfsPath}\". The file may still be in use. Retrying...`\n              );\n            }\n            throw error;\n          }\n        },\n        4,\n        1500\n      ); // 4 retries with 1.5s base delay (1.5s, 3s, 6s, 12s)\n\n      const connection = await db.connect();\n      validateConnection(connection);\n\n      // Verify connection with a basic query\n      await connection.query(`SHOW TABLES`);\n\n      return { db, connection };\n    } catch (error) {\n      opfsActivePaths.delete(opfsPath);\n      await db.terminate().catch(() => {});\n      throw error;\n    }\n  };\n\n  const timeoutPromise = new Promise<never>((_, reject) => {\n    setTimeout(\n      () =>\n        reject(\n          new Error(\n            `OPFS connection timed out after ${TIMEOUT_MS / 1000}s. ` +\n              `This usually means the server is missing Cross-Origin-Opener-Policy ` +\n              `and Cross-Origin-Embedder-Policy headers required for SharedArrayBuffer.`\n          )\n        ),\n      TIMEOUT_MS\n    );\n  });\n\n  try {\n    return await Promise.race([connectionLogic(), timeoutPromise]);\n  } catch (error) {\n    opfsActivePaths.delete(opfsPath);\n    throw error;\n  }\n};\n"
  },
  {
    "path": "src/services/duckdb/resultParser.ts",
    "content": "import type { QueryResult, ExternalQueryResponse } from \"@/store/types\";\n\n/**\n * Converts a raw result (from an external HTTP endpoint) into a QueryResult.\n * Handles JSONCompact format, NDJSON, and plain JSON array-of-objects responses.\n * See: https://github.com/caioricciuti/duck-ui/issues/24\n */\nexport const rawResultToJSON = (rawResult: string): QueryResult => {\n  const trimmed = rawResult.trim();\n  if (!trimmed) {\n    return { columns: [], columnTypes: [], data: [], rowCount: 0 };\n  }\n\n  try {\n    // Try parsing as single JSON first (standard format)\n    let parsed: Partial<ExternalQueryResponse> | undefined;\n\n    try {\n      const json = JSON.parse(trimmed);\n\n      // Handle array-of-objects format (default httpserver response without JSONCompact)\n      // e.g. [{\"col1\": \"val1\", \"col2\": \"val2\"}, ...]\n      if (Array.isArray(json) && json.length > 0 && !(\"meta\" in json[0])) {\n        const columns = Object.keys(json[0]);\n        const data = json.map((row: Record<string, unknown>) => ({ ...row }));\n        return {\n          columns,\n          columnTypes: columns.map(() => \"VARCHAR\"),\n          data,\n          rowCount: data.length,\n        };\n      }\n\n      parsed = json;\n    } catch {\n      // If single JSON fails, try NDJSON (newline-delimited JSON)\n      // DuckDB httpserver may return multiple JSON objects, one per line\n      const lines = trimmed.split(\"\\n\").filter((line) => line.trim());\n\n      if (lines.length === 0) {\n        return { columns: [], columnTypes: [], data: [], rowCount: 0 };\n      }\n\n      // Parse each line as JSON\n      const objects = lines.map((line, idx) => {\n        try {\n          return JSON.parse(line);\n        } catch {\n          throw new Error(\n            `Failed to parse NDJSON line ${idx + 1}/${lines.length}: \"${line.substring(0, 80)}${line.length > 80 ? \"...\" : \"\"}\"`\n          );\n        }\n      });\n\n      // If all lines are plain objects (array-of-objects NDJSON), treat as rows\n      if (\n        objects.length > 0 &&\n        objects.every(\n          (obj) => obj && typeof obj === \"object\" && !(\"meta\" in obj) && !(\"data\" in obj)\n        )\n      ) {\n        const columns = Object.keys(objects[0]);\n        return {\n          columns,\n          columnTypes: columns.map(() => \"VARCHAR\"),\n          data: objects,\n          rowCount: objects.length,\n        };\n      }\n\n      // Find the result object (has meta and data)\n      const resultObj = objects.find(\n        (obj): obj is ExternalQueryResponse =>\n          obj && typeof obj === \"object\" && \"meta\" in obj && \"data\" in obj\n      );\n\n      if (resultObj) {\n        parsed = resultObj;\n      } else {\n        // If no single result object, try to merge (meta from one, data from others)\n        const metaObj = objects.find((obj) => obj?.meta);\n        const dataObj = objects.find((obj) => obj?.data);\n\n        if (metaObj && dataObj) {\n          parsed = {\n            meta: metaObj.meta,\n            data: dataObj.data,\n            rows: dataObj.rows || dataObj.data?.length || 0,\n          };\n        } else {\n          const preview = trimmed.substring(0, 200);\n          throw new Error(\n            `Unrecognized response format. Expected JSONCompact (with \"meta\" and \"data\" fields) ` +\n              `or array of objects. Response preview: ${preview}${trimmed.length > 200 ? \"...\" : \"\"}`\n          );\n        }\n      }\n    }\n\n    // Validate required fields\n    if (!parsed || typeof parsed !== \"object\") {\n      throw new Error(\n        `Invalid response: expected a JSON object but got ${typeof parsed}. ` +\n          `Response preview: ${trimmed.substring(0, 200)}${trimmed.length > 200 ? \"...\" : \"\"}`\n      );\n    }\n\n    if (\n      !parsed.meta ||\n      !parsed.data ||\n      !Array.isArray(parsed.meta) ||\n      !Array.isArray(parsed.data)\n    ) {\n      const hasKeys = parsed ? Object.keys(parsed).join(\", \") : \"none\";\n      throw new Error(\n        `Invalid response format: expected \"meta\" (array) and \"data\" (array) fields. ` +\n          `Found keys: [${hasKeys}]. Ensure the DuckDB httpserver is returning JSONCompact format.`\n      );\n    }\n\n    // Convert to QueryResult format\n    const columns = parsed.meta.map((m) => m.name);\n    const columnTypes = parsed.meta.map((m) => m.type);\n    const data = parsed.data.map((row: unknown, rowIdx: number) => {\n      if (!Array.isArray(row)) {\n        // If rows are objects instead of arrays, use them directly\n        if (row && typeof row === \"object\") {\n          return row as Record<string, unknown>;\n        }\n        throw new Error(`Invalid row format at index ${rowIdx}: expected array or object`);\n      }\n      const rowObject: Record<string, unknown> = {};\n      columns.forEach((col, index) => {\n        rowObject[col] = row[index];\n      });\n      return rowObject;\n    });\n\n    return {\n      columns,\n      columnTypes,\n      data,\n      rowCount: parsed.rows || data.length,\n    };\n  } catch (error) {\n    throw new Error(\n      `Failed to parse query result: ${error instanceof Error ? error.message : \"Unknown error\"}`\n    );\n  }\n};\n\n/**\n * Converts a WASM query result into a QueryResult.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const resultToJSON = (result: any): QueryResult => {\n  try {\n    const schema = result.schema;\n    const fields = schema.fields;\n\n    // Pre-extract column vectors for Decimal types\n    const columnVectors = fields.map((_: unknown, colIdx: number) => result.getChildAt(colIdx));\n\n    // Use the standard toArray().map() approach, but fix Decimal values from column vectors\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const data = result.toArray().map((row: any, rowIndex: number) => {\n      const jsonRow = row.toJSON();\n\n      // Fix Decimal types by reading directly from column vectors\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      fields.forEach((field: any, columnIndex: number) => {\n        const col = field.name;\n        const type = field.type.toString();\n\n        // Only fix Decimal types - they come as null from toJSON()\n        if (type.includes(\"Decimal\")) {\n          try {\n            // Get the value directly from the column vector\n            const value = columnVectors[columnIndex].get(rowIndex);\n\n            if (value !== null && value !== undefined) {\n              // Convert Decimal object to number\n              // Arrow Decimals store unscaled values - we need to apply the scale\n              if (typeof value === \"object\" && typeof value.valueOf === \"function\") {\n                const unscaledValue = Number(value.valueOf());\n                const scale = field.type.scale || 0; // Get scale from Arrow type metadata\n                const scaledValue = unscaledValue / Math.pow(10, scale);\n                jsonRow[col] = scaledValue;\n              } else if (typeof value === \"number\") {\n                jsonRow[col] = value;\n              } else if (typeof value === \"string\") {\n                const parsed = parseFloat(value);\n                jsonRow[col] = isNaN(parsed) ? null : parsed;\n              } else {\n                jsonRow[col] = null;\n              }\n            }\n          } catch (error) {\n            console.error(`Error processing Decimal column ${col} at row ${rowIndex}:`, error);\n          }\n        }\n        // Fix Date types\n        else if (type === \"Date32<DAY>\") {\n          const value = jsonRow[col];\n          if (value !== null && value !== undefined) {\n            jsonRow[col] = new Date(value).toLocaleDateString();\n          }\n        }\n        // Fix Timestamp types\n        else if (type === \"Date64<MILLISECOND>\" || type === \"Timestamp<MICROSECOND>\") {\n          const value = jsonRow[col];\n          if (value !== null && value !== undefined) {\n            jsonRow[col] = new Date(value);\n          }\n        }\n      });\n\n      return jsonRow;\n    });\n\n    return {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      columns: fields.map((field: any) => field.name),\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      columnTypes: fields.map((field: any) => field.type.toString()),\n      data,\n      rowCount: result.numRows,\n    };\n  } catch (error) {\n    console.error(\"Error converting query result to JSON:\", error);\n    return {\n      columns: [],\n      columnTypes: [],\n      data: [],\n      rowCount: 0,\n      error: `Failed to process query results: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n    };\n  }\n};\n"
  },
  {
    "path": "src/services/duckdb/schemaFetcher.ts",
    "content": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { sqlEscapeIdentifier, sqlEscapeString } from \"@/lib/sqlSanitize\";\nimport type { ColumnInfo, TableInfo, DatabaseInfo } from \"@/store/types\";\n\n/**\n * Fetches databases and tables using the WASM connection.\n */\nexport const fetchWasmDatabases = async (\n  connection: duckdb.AsyncDuckDBConnection\n): Promise<DatabaseInfo[]> => {\n  const dbListResult = await connection.query(`PRAGMA database_list`);\n  return Promise.all(\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    dbListResult.toArray().map(async (db: any) => {\n      const dbName = db.name.toString();\n      const tablesResult = await connection.query(\n        `SELECT table_name FROM information_schema.tables WHERE table_catalog = '${sqlEscapeString(dbName)}'`\n      );\n      const tables: TableInfo[] = await Promise.all(\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        tablesResult.toArray().map(async (tbl: any) => {\n          const tableName = tbl.table_name.toString();\n          const columnsResult = await connection.query(\n            `DESCRIBE ${sqlEscapeIdentifier(dbName)}.${sqlEscapeIdentifier(tableName)}`\n          );\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          const columns: ColumnInfo[] = columnsResult.toArray().map((col: any) => ({\n            name: col.column_name.toString(),\n            type: col.column_type.toString(),\n            nullable: col.null === \"YES\",\n          }));\n          const countResult = await connection.query(\n            `SELECT COUNT(*) as count FROM ${sqlEscapeIdentifier(dbName)}.${sqlEscapeIdentifier(tableName)}`\n          );\n          // Assumes countResult.toArray() returns a 2D array where the first element is the count.\n          const countValue = Number(countResult.toArray()[0][0]);\n          return {\n            name: tableName,\n            schema: dbName,\n            columns,\n            rowCount: countValue,\n            createdAt: new Date().toISOString(),\n          };\n        })\n      );\n      return { name: dbName, tables };\n    })\n  );\n};\n"
  },
  {
    "path": "src/services/duckdb/utils.ts",
    "content": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { generateUUID } from \"@/lib/utils\";\nimport type { QueryHistoryItem } from \"@/store/types\";\n\n/**\n * Exponential backoff retry helper.\n */\nexport const retryWithBackoff = async <T>(\n  operation: () => Promise<T>,\n  maxRetries: number = 3,\n  baseDelay: number = 1000\n): Promise<T> => {\n  let lastError: Error | null = null;\n\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      return await operation();\n    } catch (error) {\n      lastError = error as Error;\n\n      if (attempt < maxRetries - 1) {\n        const delay = baseDelay * Math.pow(2, attempt);\n        await new Promise((resolve) => setTimeout(resolve, delay));\n      }\n    }\n  }\n\n  throw lastError || new Error(\"Operation failed after retries\");\n};\n\n/**\n * Validate a DuckDB connection.\n */\nexport const validateConnection = (\n  connection: duckdb.AsyncDuckDBConnection | null\n): duckdb.AsyncDuckDBConnection => {\n  if (!connection || typeof connection.query !== \"function\") {\n    throw new Error(\"Database connection is not valid\");\n  }\n  return connection;\n};\n\n/**\n * Helper to update query history.\n */\nexport const updateHistory = (\n  currentHistory: QueryHistoryItem[],\n  query: string,\n  errorMsg?: string\n): QueryHistoryItem[] => {\n  const newItem: QueryHistoryItem = {\n    id: generateUUID(),\n    query,\n    timestamp: new Date(),\n    ...(errorMsg ? { error: errorMsg } : {}),\n  };\n  const existingIndex = currentHistory.findIndex((item) => item.query === query);\n  const newHistory =\n    existingIndex !== -1\n      ? [newItem, ...currentHistory.filter((_, idx) => idx !== existingIndex)]\n      : [newItem, ...currentHistory];\n  return newHistory.slice(0, 15);\n};\n"
  },
  {
    "path": "src/services/duckdb/wasmConnection.ts",
    "content": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport { sqlEscapeString, sqlEscapeIdentifier } from \"@/lib/sqlSanitize\";\nimport { validateConnection } from \"./utils\";\n\nconst getRuntimeEnv = (): Partial<NonNullable<Window[\"env\"]>> =>\n  (window.env ?? {}) as Partial<NonNullable<Window[\"env\"]>>;\n\nconst getCdnBundles = (runtimeEnv: Partial<NonNullable<Window[\"env\"]>>): duckdb.DuckDBBundles => {\n  const configuredBaseUrl = runtimeEnv.DUCK_UI_DUCKDB_WASM_BASE_URL ?? \"\";\n  if (!configuredBaseUrl) {\n    return duckdb.getJsDelivrBundles();\n  }\n\n  const baseUrl = configuredBaseUrl.replace(/\\/+$/, \"\");\n\n  return {\n    mvp: {\n      mainModule: `${baseUrl}/duckdb-mvp.wasm`,\n      mainWorker: `${baseUrl}/duckdb-browser-mvp.worker.js`,\n    },\n    eh: {\n      mainModule: `${baseUrl}/duckdb-eh.wasm`,\n      mainWorker: `${baseUrl}/duckdb-browser-eh.worker.js`,\n    },\n  };\n};\n\nexport const resolveDuckdbBundles: () => Promise<duckdb.DuckDBBundles> =\n  __DUCK_UI_BUILD_DUCKDB_CDN_ONLY__\n    ? async () => getCdnBundles(getRuntimeEnv())\n    : (() => {\n        let localBundlesPromise: Promise<duckdb.DuckDBBundles> | null = null;\n\n        const getLocalBundles = async (): Promise<duckdb.DuckDBBundles> => {\n          if (!localBundlesPromise) {\n            localBundlesPromise = Promise.all([\n              import(\"@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm?url\"),\n              import(\"@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js?url\"),\n              import(\"@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url\"),\n              import(\"@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js?url\"),\n            ])\n              .then(([duckdbWasm, mvpWorker, duckdbWasmEh, ehWorker]) => ({\n                mvp: {\n                  mainModule: duckdbWasm.default,\n                  mainWorker: mvpWorker.default,\n                },\n                eh: {\n                  mainModule: duckdbWasmEh.default,\n                  mainWorker: ehWorker.default,\n                },\n              }))\n              .catch((error) => {\n                // Allow retry after transient chunk/load failures.\n                localBundlesPromise = null;\n                throw error;\n              });\n          }\n\n          return localBundlesPromise;\n        };\n\n        return async () => {\n          const runtimeEnv = getRuntimeEnv();\n          if (runtimeEnv.DUCK_UI_DUCKDB_WASM_USE_CDN === true) {\n            return getCdnBundles(runtimeEnv);\n          }\n          return getLocalBundles();\n        };\n      })();\n\nexport const createDuckdbWorker = (\n  mainWorkerUrl: string\n): { worker: Worker; revoke: () => void } => {\n  if (/^https?:\\/\\//i.test(mainWorkerUrl)) {\n    const workerUrl = URL.createObjectURL(\n      new Blob([`importScripts(${JSON.stringify(mainWorkerUrl)});`], { type: \"text/javascript\" })\n    );\n    return {\n      worker: new Worker(workerUrl),\n      revoke: () => URL.revokeObjectURL(workerUrl),\n    };\n  }\n\n  return {\n    worker: new Worker(mainWorkerUrl),\n    revoke: () => {},\n  };\n};\n\n/**\n * Initializes a new DuckDB WASM connection.\n */\nexport const initializeWasmConnection = async (): Promise<{\n  db: duckdb.AsyncDuckDB;\n  connection: duckdb.AsyncDuckDBConnection;\n}> => {\n  const bundles = await resolveDuckdbBundles();\n  const bundle = await duckdb.selectBundle(bundles);\n  if (!bundle.mainWorker) {\n    throw new Error(\"DuckDB WASM bundle is missing the main worker URL\");\n  }\n  const { worker, revoke } = createDuckdbWorker(bundle.mainWorker);\n  const logger = new duckdb.VoidLogger();\n\n  // Check if unsigned extensions are allowed from environment\n  const allowUnsignedExtensions = window.env?.DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS || false;\n\n  // Create database with configuration\n  const db = new duckdb.AsyncDuckDB(logger, worker);\n\n  try {\n    await db.instantiate(bundle.mainModule);\n  } finally {\n    revoke();\n  }\n\n  try {\n    const dbConfig: duckdb.DuckDBConfig = {\n      allowUnsignedExtensions: allowUnsignedExtensions,\n    };\n\n    await db.open(dbConfig);\n\n    const connection = await db.connect();\n    // Validate immediately\n    validateConnection(connection);\n\n    // Install and load extensions (non-blocking for offline support)\n    try {\n      await connection.query(`INSTALL excel`);\n      await connection.query(`LOAD excel`);\n    } catch (error) {\n      console.warn(\"[DuckDB] Failed to install/load excel extension:\", error);\n    }\n\n    // Load embedded databases from public/databases/\n    await loadEmbeddedDatabases(db, connection);\n\n    return { db, connection };\n  } catch (error) {\n    await db.terminate();\n    throw error;\n  }\n};\n\n/**\n * Loads embedded databases from the public/databases directory.\n */\nexport const loadEmbeddedDatabases = async (\n  db: duckdb.AsyncDuckDB,\n  connection: duckdb.AsyncDuckDBConnection\n): Promise<void> => {\n  try {\n    // Fetch the manifest file\n    const manifestResponse = await fetch(\"/databases/manifest.json\");\n    if (!manifestResponse.ok) {\n      console.debug(\"No embedded databases manifest found\");\n      return;\n    }\n\n    const manifest = await manifestResponse.json();\n    const databases = manifest.databases || [];\n\n    if (databases.length === 0) {\n      console.debug(\"No embedded databases configured\");\n      return;\n    }\n\n    console.info(`Loading ${databases.length} embedded database(s)...`);\n\n    // Load each database\n    for (const dbConfig of databases) {\n      if (!dbConfig.autoLoad) {\n        console.info(`Skipping ${dbConfig.name} (autoLoad: false)`);\n        continue;\n      }\n\n      try {\n        console.info(`Loading embedded database: ${dbConfig.name}`);\n\n        // Fetch the database file\n        const dbFileResponse = await fetch(`/databases/${dbConfig.file}`);\n        if (!dbFileResponse.ok) {\n          console.error(`Failed to fetch ${dbConfig.file}: ${dbFileResponse.statusText}`);\n          continue;\n        }\n\n        const arrayBuffer = await dbFileResponse.arrayBuffer();\n        const buffer = new Uint8Array(arrayBuffer);\n\n        // Register the file in DuckDB's virtual file system\n        const fileName = dbConfig.file;\n        await db.registerFileBuffer(fileName, buffer);\n\n        // Attach the database (derive alias from filename without extension)\n        const dbAlias = fileName.replace(/\\.db$/i, \"\").replace(/[^a-zA-Z0-9_]/g, \"_\");\n        await connection.query(\n          `ATTACH DATABASE '${sqlEscapeString(fileName)}' AS ${sqlEscapeIdentifier(dbAlias)}`\n        );\n\n        console.info(`Successfully loaded embedded database: ${dbConfig.name} as ${dbAlias}`);\n      } catch (error) {\n        console.error(`Error loading embedded database ${dbConfig.name}:`, error);\n      }\n    }\n  } catch (error) {\n    console.error(\"Error loading embedded databases:\", error);\n  }\n};\n"
  },
  {
    "path": "src/services/persistence/__tests__/crypto.test.ts",
    "content": "import { describe, it, expect } from \"vitest\";\nimport {\n  generateEncryptionKey,\n  generateSalt,\n  deriveKeyFromPassword,\n  encrypt,\n  decrypt,\n  exportKey,\n  importKey,\n} from \"../crypto\";\n\ndescribe(\"crypto\", () => {\n  describe(\"generateEncryptionKey\", () => {\n    it(\"generates a CryptoKey with AES-GCM algorithm\", async () => {\n      const key = await generateEncryptionKey();\n      expect(key).toBeDefined();\n      expect(key.algorithm).toMatchObject({ name: \"AES-GCM\", length: 256 });\n      expect(key.extractable).toBe(true);\n      expect(key.usages).toContain(\"encrypt\");\n      expect(key.usages).toContain(\"decrypt\");\n    });\n\n    it(\"generates unique keys each time\", async () => {\n      const key1 = await generateEncryptionKey();\n      const key2 = await generateEncryptionKey();\n      const exported1 = await exportKey(key1);\n      const exported2 = await exportKey(key2);\n      expect(exported1).not.toBe(exported2);\n    });\n  });\n\n  describe(\"generateSalt\", () => {\n    it(\"generates a 16-byte Uint8Array\", () => {\n      const salt = generateSalt();\n      expect(salt).toBeInstanceOf(Uint8Array);\n      expect(salt.length).toBe(16);\n    });\n\n    it(\"generates unique salts each time\", () => {\n      const salt1 = generateSalt();\n      const salt2 = generateSalt();\n      expect(Array.from(salt1)).not.toEqual(Array.from(salt2));\n    });\n  });\n\n  describe(\"encrypt / decrypt\", () => {\n    it(\"round-trips a simple string\", async () => {\n      const key = await generateEncryptionKey();\n      const plaintext = \"hello world\";\n      const ciphertext = await encrypt(plaintext, key);\n      const decrypted = await decrypt(ciphertext, key);\n      expect(decrypted).toBe(plaintext);\n    });\n\n    it(\"round-trips an empty string\", async () => {\n      const key = await generateEncryptionKey();\n      const ciphertext = await encrypt(\"\", key);\n      const decrypted = await decrypt(ciphertext, key);\n      expect(decrypted).toBe(\"\");\n    });\n\n    it(\"round-trips unicode content\", async () => {\n      const key = await generateEncryptionKey();\n      const plaintext = \"🦆 duck-ui — ñ, é, 中文\";\n      const ciphertext = await encrypt(plaintext, key);\n      const decrypted = await decrypt(ciphertext, key);\n      expect(decrypted).toBe(plaintext);\n    });\n\n    it(\"round-trips a JSON API key\", async () => {\n      const key = await generateEncryptionKey();\n      const apiKey = \"sk-proj-abc123XYZ_long-api-key-value\";\n      const ciphertext = await encrypt(apiKey, key);\n      const decrypted = await decrypt(ciphertext, key);\n      expect(decrypted).toBe(apiKey);\n    });\n\n    it(\"produces different ciphertexts for the same plaintext (random IV)\", async () => {\n      const key = await generateEncryptionKey();\n      const plaintext = \"same input\";\n      const c1 = await encrypt(plaintext, key);\n      const c2 = await encrypt(plaintext, key);\n      expect(c1).not.toBe(c2);\n    });\n\n    it(\"ciphertext is a base64 string\", async () => {\n      const key = await generateEncryptionKey();\n      const ciphertext = await encrypt(\"test\", key);\n      expect(typeof ciphertext).toBe(\"string\");\n      // base64 regex\n      expect(ciphertext).toMatch(/^[A-Za-z0-9+/]+=*$/);\n    });\n\n    it(\"fails to decrypt with a different key\", async () => {\n      const key1 = await generateEncryptionKey();\n      const key2 = await generateEncryptionKey();\n      const ciphertext = await encrypt(\"secret\", key1);\n      await expect(decrypt(ciphertext, key2)).rejects.toThrow();\n    });\n  });\n\n  describe(\"exportKey / importKey\", () => {\n    it(\"round-trips a key\", async () => {\n      const original = await generateEncryptionKey();\n      const exported = await exportKey(original);\n      const imported = await importKey(exported);\n\n      // Verify the imported key works\n      const ciphertext = await encrypt(\"test\", original);\n      const decrypted = await decrypt(ciphertext, imported);\n      expect(decrypted).toBe(\"test\");\n    });\n\n    it(\"exported key is a base64 string\", async () => {\n      const key = await generateEncryptionKey();\n      const exported = await exportKey(key);\n      expect(typeof exported).toBe(\"string\");\n      expect(exported).toMatch(/^[A-Za-z0-9+/]+=*$/);\n    });\n\n    it(\"imported key has correct properties\", async () => {\n      const original = await generateEncryptionKey();\n      const exported = await exportKey(original);\n      const imported = await importKey(exported);\n\n      expect(imported.algorithm).toMatchObject({ name: \"AES-GCM\", length: 256 });\n      expect(imported.extractable).toBe(true);\n      expect(imported.usages).toContain(\"encrypt\");\n      expect(imported.usages).toContain(\"decrypt\");\n    });\n  });\n\n  describe(\"deriveKeyFromPassword\", () => {\n    it(\"derives a usable key from password + salt\", async () => {\n      const salt = generateSalt();\n      const key = await deriveKeyFromPassword(\"my-secure-password\", salt);\n\n      expect(key.algorithm).toMatchObject({ name: \"AES-GCM\", length: 256 });\n      const ciphertext = await encrypt(\"secret data\", key);\n      const decrypted = await decrypt(ciphertext, key);\n      expect(decrypted).toBe(\"secret data\");\n    });\n\n    it(\"same password + same salt produces same key\", async () => {\n      const salt = generateSalt();\n      const key1 = await deriveKeyFromPassword(\"password123\", salt);\n      const key2 = await deriveKeyFromPassword(\"password123\", salt);\n      const exported1 = await exportKey(key1);\n      const exported2 = await exportKey(key2);\n      expect(exported1).toBe(exported2);\n    });\n\n    it(\"same password + different salt produces different key\", async () => {\n      const salt1 = generateSalt();\n      const salt2 = generateSalt();\n      const key1 = await deriveKeyFromPassword(\"password123\", salt1);\n      const key2 = await deriveKeyFromPassword(\"password123\", salt2);\n      const exported1 = await exportKey(key1);\n      const exported2 = await exportKey(key2);\n      expect(exported1).not.toBe(exported2);\n    });\n\n    it(\"different password + same salt produces different key\", async () => {\n      const salt = generateSalt();\n      const key1 = await deriveKeyFromPassword(\"password-A\", salt);\n      const key2 = await deriveKeyFromPassword(\"password-B\", salt);\n      const exported1 = await exportKey(key1);\n      const exported2 = await exportKey(key2);\n      expect(exported1).not.toBe(exported2);\n    });\n\n    it(\"data encrypted with derived key cannot be decrypted with wrong password\", async () => {\n      const salt = generateSalt();\n      const rightKey = await deriveKeyFromPassword(\"correct-password\", salt);\n      const wrongKey = await deriveKeyFromPassword(\"wrong-password\", salt);\n      const ciphertext = await encrypt(\"sensitive data\", rightKey);\n      await expect(decrypt(ciphertext, wrongKey)).rejects.toThrow();\n    });\n  });\n});\n"
  },
  {
    "path": "src/services/persistence/__tests__/migrations.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { describe, it, expect, vi } from \"vitest\";\nimport { runMigrations } from \"../migrations\";\n\nfunction createMockConnection() {\n  const queries: string[] = [];\n\n  // Simulate schema_version table state\n  let currentVersion = 0;\n\n  const mockConn = {\n    query: vi.fn(async (sql: string) => {\n      queries.push(sql.trim());\n\n      // Return current version when queried\n      if (sql.includes(\"MAX(version)\")) {\n        return {\n          toArray: () => [{ v: currentVersion }],\n        };\n      }\n\n      // Track version inserts\n      const versionMatch = sql.match(/INSERT INTO schema_version.*VALUES \\((\\d+)/);\n      if (versionMatch) {\n        currentVersion = parseInt(versionMatch[1], 10);\n      }\n\n      return { toArray: () => [] };\n    }),\n  };\n\n  return { mockConn, queries, getVersion: () => currentVersion };\n}\n\ndescribe(\"runMigrations\", () => {\n  it(\"creates schema_version table if not exists\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const createSchemaVersion = queries.find((q) =>\n      q.includes(\"CREATE TABLE IF NOT EXISTS schema_version\")\n    );\n    expect(createSchemaVersion).toBeDefined();\n  });\n\n  it(\"queries current version from schema_version\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const versionQuery = queries.find((q) => q.includes(\"MAX(version)\"));\n    expect(versionQuery).toBeDefined();\n  });\n\n  it(\"creates all required tables on fresh database\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const requiredTables = [\n      \"profiles\",\n      \"settings\",\n      \"connections\",\n      \"query_history\",\n      \"workspace_state\",\n      \"ai_provider_configs\",\n      \"ai_conversations\",\n      \"saved_queries\",\n    ];\n\n    for (const table of requiredTables) {\n      const createQuery = queries.find(\n        (q) => q.includes(\"CREATE TABLE IF NOT EXISTS\") && q.includes(table)\n      );\n      expect(createQuery, `Missing CREATE TABLE for ${table}`).toBeDefined();\n    }\n  });\n\n  it(\"records the migration version after applying\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const insertVersion = queries.find(\n      (q) => q.includes(\"INSERT INTO schema_version\") && q.includes(\"(1,\")\n    );\n    expect(insertVersion).toBeDefined();\n  });\n\n  it(\"skips already-applied migrations\", async () => {\n    // Simulate version 1 already applied\n    let callCount = 0;\n    const mockConn = {\n      query: vi.fn(async (sql: string) => {\n        callCount++;\n        if (sql.includes(\"MAX(version)\")) {\n          return { toArray: () => [{ v: 1 }] };\n        }\n        return { toArray: () => [] };\n      }),\n    };\n\n    await runMigrations(mockConn as any);\n\n    // Should only have: CREATE schema_version + SELECT MAX(version) = 2 calls\n    // No table creates or version inserts\n    expect(callCount).toBe(2);\n  });\n\n  it(\"is idempotent — running twice produces same result\", async () => {\n    const { mockConn: conn1, queries: q1 } = createMockConnection();\n    await runMigrations(conn1 as any);\n    const tableCreates1 = q1.filter((q) => q.includes(\"CREATE TABLE IF NOT EXISTS\")).length;\n\n    const { mockConn: conn2, queries: q2 } = createMockConnection();\n    await runMigrations(conn2 as any);\n    const tableCreates2 = q2.filter((q) => q.includes(\"CREATE TABLE IF NOT EXISTS\")).length;\n\n    expect(tableCreates1).toBe(tableCreates2);\n  });\n\n  it(\"profiles table has expected columns\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const profilesQuery = queries.find((q) => q.includes(\"CREATE TABLE IF NOT EXISTS profiles\"));\n    expect(profilesQuery).toBeDefined();\n    expect(profilesQuery).toContain(\"id VARCHAR PRIMARY KEY\");\n    expect(profilesQuery).toContain(\"name VARCHAR NOT NULL\");\n    expect(profilesQuery).toContain(\"avatar_emoji\");\n    expect(profilesQuery).toContain(\"has_password\");\n    expect(profilesQuery).toContain(\"created_at\");\n    expect(profilesQuery).toContain(\"last_active\");\n  });\n\n  it(\"connections table supports encrypted credentials\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const connQuery = queries.find((q) => q.includes(\"CREATE TABLE IF NOT EXISTS connections\"));\n    expect(connQuery).toContain(\"encrypted_credentials\");\n    expect(connQuery).toContain(\"scope VARCHAR NOT NULL\");\n    expect(connQuery).toContain(\"config VARCHAR NOT NULL\");\n  });\n\n  it(\"ai_provider_configs table supports encrypted API keys\", async () => {\n    const { mockConn, queries } = createMockConnection();\n    await runMigrations(mockConn as any);\n\n    const aiQuery = queries.find((q) =>\n      q.includes(\"CREATE TABLE IF NOT EXISTS ai_provider_configs\")\n    );\n    expect(aiQuery).toContain(\"encrypted_api_key\");\n    expect(aiQuery).toContain(\"provider VARCHAR NOT NULL\");\n  });\n});\n"
  },
  {
    "path": "src/services/persistence/crypto.ts",
    "content": "/**\n * Web Crypto API helpers for encrypting sensitive data at rest.\n * Uses AES-256-GCM for encryption and PBKDF2 for password-based key derivation.\n *\n * Key storage is handled via IndexedDB (\"duck-ui-keys\"), keeping encryption keys\n * separate from the encrypted data in the OPFS system database.\n */\n\nconst KEY_DB_NAME = \"duck-ui-keys\";\nconst KEY_STORE_NAME = \"encryption-keys\";\nconst KEY_DB_VERSION = 1;\n\nconst ALGORITHM = \"AES-GCM\";\nconst KEY_LENGTH = 256;\nconst IV_LENGTH = 12; // 96 bits recommended for AES-GCM\nconst SALT_LENGTH = 16;\nconst PBKDF2_ITERATIONS = 100_000;\n\n// ─── Base64 helpers (avoids stack overflow with large arrays) ────────────────\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n  let binary = \"\";\n  for (let i = 0; i < bytes.length; i++) {\n    binary += String.fromCharCode(bytes[i]);\n  }\n  return btoa(binary);\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n  const binary = atob(base64);\n  const bytes = new Uint8Array(binary.length);\n  for (let i = 0; i < binary.length; i++) {\n    bytes[i] = binary.charCodeAt(i);\n  }\n  return bytes;\n}\n\n// ─── IndexedDB key storage ───────────────────────────────────────────────────\n\nfunction openKeyDatabase(): Promise<IDBDatabase> {\n  return new Promise((resolve, reject) => {\n    const request = indexedDB.open(KEY_DB_NAME, KEY_DB_VERSION);\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve(request.result);\n    request.onupgradeneeded = (event) => {\n      const db = (event.target as IDBOpenDBRequest).result;\n      if (!db.objectStoreNames.contains(KEY_STORE_NAME)) {\n        db.createObjectStore(KEY_STORE_NAME, { keyPath: \"profileId\" });\n      }\n    };\n  });\n}\n\nexport async function storeKeyForProfile(\n  profileId: string,\n  key: CryptoKey,\n  salt?: Uint8Array\n): Promise<void> {\n  const exported = await exportKey(key);\n  const db = await openKeyDatabase();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(KEY_STORE_NAME, \"readwrite\");\n    const store = tx.objectStore(KEY_STORE_NAME);\n    const record: Record<string, unknown> = { profileId, key: exported };\n    if (salt) {\n      record.salt = Array.from(salt);\n    }\n    store.put(record);\n    tx.oncomplete = () => {\n      db.close();\n      resolve();\n    };\n    tx.onerror = () => {\n      db.close();\n      reject(tx.error);\n    };\n    tx.onabort = () => {\n      db.close();\n      reject(tx.error ?? new Error(\"Transaction aborted\"));\n    };\n  });\n}\n\nexport async function loadKeyForProfile(\n  profileId: string\n): Promise<{ key: CryptoKey; salt?: Uint8Array } | null> {\n  const db = await openKeyDatabase();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(KEY_STORE_NAME, \"readonly\");\n    const store = tx.objectStore(KEY_STORE_NAME);\n    const request = store.get(profileId);\n    tx.oncomplete = () => {\n      db.close();\n      const record = request.result;\n      if (!record) {\n        resolve(null);\n        return;\n      }\n      importKey(record.key)\n        .then((key) => {\n          const salt = record.salt ? new Uint8Array(record.salt) : undefined;\n          resolve({ key, salt });\n        })\n        .catch(reject);\n    };\n    tx.onerror = () => {\n      db.close();\n      reject(tx.error);\n    };\n    tx.onabort = () => {\n      db.close();\n      reject(tx.error ?? new Error(\"Transaction aborted\"));\n    };\n  });\n}\n\nexport async function deleteKeyForProfile(profileId: string): Promise<void> {\n  const db = await openKeyDatabase();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(KEY_STORE_NAME, \"readwrite\");\n    const store = tx.objectStore(KEY_STORE_NAME);\n    store.delete(profileId);\n    tx.oncomplete = () => {\n      db.close();\n      resolve();\n    };\n    tx.onerror = () => {\n      db.close();\n      reject(tx.error);\n    };\n    tx.onabort = () => {\n      db.close();\n      reject(tx.error ?? new Error(\"Transaction aborted\"));\n    };\n  });\n}\n\n// ─── Key generation & derivation ─────────────────────────────────────────────\n\nexport async function generateEncryptionKey(): Promise<CryptoKey> {\n  return crypto.subtle.generateKey({ name: ALGORITHM, length: KEY_LENGTH }, true, [\n    \"encrypt\",\n    \"decrypt\",\n  ]);\n}\n\nexport function generateSalt(): Uint8Array {\n  return crypto.getRandomValues(new Uint8Array(SALT_LENGTH));\n}\n\nexport async function deriveKeyFromPassword(\n  password: string,\n  salt: Uint8Array\n): Promise<CryptoKey> {\n  const encoder = new TextEncoder();\n  const keyMaterial = await crypto.subtle.importKey(\n    \"raw\",\n    encoder.encode(password),\n    \"PBKDF2\",\n    false,\n    [\"deriveKey\"]\n  );\n  return crypto.subtle.deriveKey(\n    {\n      name: \"PBKDF2\",\n      salt: salt as BufferSource,\n      iterations: PBKDF2_ITERATIONS,\n      hash: \"SHA-256\",\n    },\n    keyMaterial,\n    { name: ALGORITHM, length: KEY_LENGTH },\n    true,\n    [\"encrypt\", \"decrypt\"]\n  );\n}\n\n// ─── Encrypt / Decrypt ──────────────────────────────────────────────────────\n\nexport async function encrypt(plaintext: string, key: CryptoKey): Promise<string> {\n  const encoder = new TextEncoder();\n  const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));\n  const ciphertext = await crypto.subtle.encrypt(\n    { name: ALGORITHM, iv },\n    key,\n    encoder.encode(plaintext)\n  );\n  // Concatenate IV + ciphertext and base64 encode\n  const combined = new Uint8Array(iv.length + ciphertext.byteLength);\n  combined.set(iv, 0);\n  combined.set(new Uint8Array(ciphertext), iv.length);\n  return uint8ArrayToBase64(combined);\n}\n\nexport async function decrypt(encoded: string, key: CryptoKey): Promise<string> {\n  const combined = base64ToUint8Array(encoded);\n  const iv = combined.slice(0, IV_LENGTH);\n  const ciphertext = combined.slice(IV_LENGTH);\n  const plaintext = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext);\n  return new TextDecoder().decode(plaintext);\n}\n\n// ─── Key export / import (for IndexedDB storage) ────────────────────────────\n\nexport async function exportKey(key: CryptoKey): Promise<string> {\n  const raw = await crypto.subtle.exportKey(\"raw\", key);\n  return uint8ArrayToBase64(new Uint8Array(raw));\n}\n\nexport async function importKey(exported: string): Promise<CryptoKey> {\n  const raw = base64ToUint8Array(exported);\n  return crypto.subtle.importKey(\n    \"raw\",\n    raw.buffer as ArrayBuffer,\n    { name: ALGORITHM, length: KEY_LENGTH },\n    true,\n    [\"encrypt\", \"decrypt\"]\n  );\n}\n"
  },
  {
    "path": "src/services/persistence/fallback.ts",
    "content": "/**\n * IndexedDB fallback for browsers without OPFS support (e.g., Firefox).\n * Implements the same data access patterns as the DuckDB-based repositories\n * but stores data as JSON documents in IndexedDB object stores.\n */\n\nconst FALLBACK_DB_NAME = \"duck-ui-persistence\";\nconst FALLBACK_DB_VERSION = 1;\n\nconst STORES = [\n  \"profiles\",\n  \"settings\",\n  \"connections\",\n  \"query_history\",\n  \"workspace_state\",\n  \"ai_provider_configs\",\n  \"ai_conversations\",\n  \"saved_queries\",\n] as const;\n\nlet fallbackDb: IDBDatabase | null = null;\n\nfunction openFallbackDb(): Promise<IDBDatabase> {\n  if (fallbackDb) return Promise.resolve(fallbackDb);\n\n  return new Promise((resolve, reject) => {\n    const request = indexedDB.open(FALLBACK_DB_NAME, FALLBACK_DB_VERSION);\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => {\n      fallbackDb = request.result;\n      resolve(fallbackDb);\n    };\n    request.onupgradeneeded = (event) => {\n      const db = (event.target as IDBOpenDBRequest).result;\n      for (const storeName of STORES) {\n        if (!db.objectStoreNames.contains(storeName)) {\n          if (storeName === \"settings\") {\n            db.createObjectStore(storeName, { keyPath: [\"profile_id\", \"category\", \"key\"] });\n          } else if (storeName === \"ai_provider_configs\") {\n            db.createObjectStore(storeName, { keyPath: [\"profile_id\", \"provider\"] });\n          } else if (storeName === \"workspace_state\") {\n            db.createObjectStore(storeName, { keyPath: \"profile_id\" });\n          } else {\n            db.createObjectStore(storeName, { keyPath: \"id\" });\n          }\n        }\n      }\n    };\n  });\n}\n\ntype StoreName = (typeof STORES)[number];\n\nexport async function fallbackPut(\n  store: StoreName,\n  record: Record<string, unknown>\n): Promise<void> {\n  const db = await openFallbackDb();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(store, \"readwrite\");\n    const os = tx.objectStore(store);\n    const request = os.put(record);\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve();\n  });\n}\n\nexport async function fallbackGet(store: StoreName, key: IDBValidKey): Promise<unknown | null> {\n  const db = await openFallbackDb();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(store, \"readonly\");\n    const os = tx.objectStore(store);\n    const request = os.get(key);\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve(request.result ?? null);\n  });\n}\n\nexport async function fallbackGetAll(store: StoreName): Promise<unknown[]> {\n  const db = await openFallbackDb();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(store, \"readonly\");\n    const os = tx.objectStore(store);\n    const request = os.getAll();\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve(request.result);\n  });\n}\n\nexport async function fallbackDelete(store: StoreName, key: IDBValidKey): Promise<void> {\n  const db = await openFallbackDb();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(store, \"readwrite\");\n    const os = tx.objectStore(store);\n    const request = os.delete(key);\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve();\n  });\n}\n\nexport async function fallbackClear(store: StoreName): Promise<void> {\n  const db = await openFallbackDb();\n  return new Promise((resolve, reject) => {\n    const tx = db.transaction(store, \"readwrite\");\n    const os = tx.objectStore(store);\n    const request = os.clear();\n    request.onerror = () => reject(request.error);\n    request.onsuccess = () => resolve();\n  });\n}\n"
  },
  {
    "path": "src/services/persistence/index.ts",
    "content": "export {\n  initializeSystemDb,\n  getSystemConnection,\n  closeSystemDb,\n  isOpfsAvailable,\n  isUsingOpfs,\n  isSystemDbInitialized,\n} from \"./systemDb\";\n\nexport {\n  generateEncryptionKey,\n  deriveKeyFromPassword,\n  encrypt,\n  decrypt,\n  exportKey,\n  importKey,\n  generateSalt,\n  storeKeyForProfile,\n  loadKeyForProfile,\n  deleteKeyForProfile,\n} from \"./crypto\";\n\nexport { runMigrations } from \"./migrations\";\n\nexport * from \"./repositories\";\n"
  },
  {
    "path": "src/services/persistence/migrations.ts",
    "content": "/**\n * Schema migration runner for the system database.\n * Tracks applied versions in `schema_version` table and runs pending migrations in order.\n */\n\nimport type * as duckdb from \"@duckdb/duckdb-wasm\";\n\ninterface Migration {\n  version: number;\n  description: string;\n  sql: string[];\n}\n\nconst MIGRATIONS: Migration[] = [\n  {\n    version: 1,\n    description:\n      \"Initial schema — profiles, settings, connections, history, workspace, AI, saved queries\",\n    sql: [\n      `CREATE TABLE IF NOT EXISTS profiles (\n        id VARCHAR PRIMARY KEY,\n        name VARCHAR NOT NULL,\n        avatar_emoji VARCHAR DEFAULT '🦆',\n        has_password BOOLEAN DEFAULT false,\n        password_verify_token VARCHAR,\n        created_at TIMESTAMP DEFAULT current_timestamp,\n        last_active TIMESTAMP DEFAULT current_timestamp\n      )`,\n      `CREATE TABLE IF NOT EXISTS settings (\n        profile_id VARCHAR NOT NULL,\n        category VARCHAR NOT NULL,\n        key VARCHAR NOT NULL,\n        value VARCHAR NOT NULL,\n        PRIMARY KEY (profile_id, category, key)\n      )`,\n      `CREATE TABLE IF NOT EXISTS connections (\n        id VARCHAR PRIMARY KEY,\n        profile_id VARCHAR NOT NULL,\n        name VARCHAR NOT NULL,\n        scope VARCHAR NOT NULL,\n        config VARCHAR NOT NULL,\n        encrypted_credentials VARCHAR,\n        environment VARCHAR DEFAULT 'APP',\n        created_at TIMESTAMP DEFAULT current_timestamp\n      )`,\n      `CREATE TABLE IF NOT EXISTS query_history (\n        id VARCHAR PRIMARY KEY,\n        profile_id VARCHAR NOT NULL,\n        connection_id VARCHAR,\n        sql_text VARCHAR NOT NULL,\n        error VARCHAR,\n        duration_ms INTEGER,\n        row_count INTEGER,\n        executed_at TIMESTAMP DEFAULT current_timestamp\n      )`,\n      `CREATE TABLE IF NOT EXISTS workspace_state (\n        profile_id VARCHAR PRIMARY KEY,\n        tabs VARCHAR NOT NULL,\n        active_tab_id VARCHAR,\n        current_connection_id VARCHAR,\n        current_database VARCHAR,\n        updated_at TIMESTAMP DEFAULT current_timestamp\n      )`,\n      `CREATE TABLE IF NOT EXISTS ai_provider_configs (\n        profile_id VARCHAR NOT NULL,\n        provider VARCHAR NOT NULL,\n        config VARCHAR NOT NULL,\n        encrypted_api_key VARCHAR,\n        PRIMARY KEY (profile_id, provider)\n      )`,\n      `CREATE TABLE IF NOT EXISTS ai_conversations (\n        id VARCHAR PRIMARY KEY,\n        profile_id VARCHAR NOT NULL,\n        title VARCHAR,\n        messages VARCHAR NOT NULL,\n        provider VARCHAR,\n        created_at TIMESTAMP DEFAULT current_timestamp,\n        updated_at TIMESTAMP DEFAULT current_timestamp\n      )`,\n      `CREATE TABLE IF NOT EXISTS saved_queries (\n        id VARCHAR PRIMARY KEY,\n        profile_id VARCHAR NOT NULL,\n        name VARCHAR NOT NULL,\n        sql_text VARCHAR NOT NULL,\n        description VARCHAR,\n        tags VARCHAR,\n        folder VARCHAR DEFAULT 'default',\n        created_at TIMESTAMP DEFAULT current_timestamp,\n        updated_at TIMESTAMP DEFAULT current_timestamp\n      )`,\n    ],\n  },\n];\n\nexport async function runMigrations(conn: duckdb.AsyncDuckDBConnection): Promise<void> {\n  // Ensure schema_version table exists\n  await conn.query(`\n    CREATE TABLE IF NOT EXISTS schema_version (\n      version INTEGER PRIMARY KEY,\n      applied_at TIMESTAMP DEFAULT current_timestamp,\n      description VARCHAR\n    )\n  `);\n\n  // Get current version\n  const result = await conn.query(`SELECT COALESCE(MAX(version), 0) as v FROM schema_version`);\n  const currentVersion = Number(result.toArray()[0]?.v ?? 0);\n\n  // Apply pending migrations\n  for (const migration of MIGRATIONS) {\n    if (migration.version <= currentVersion) continue;\n\n    console.info(`[SystemDB] Applying migration v${migration.version}: ${migration.description}`);\n\n    for (const sql of migration.sql) {\n      await conn.query(sql);\n    }\n\n    await conn.query(\n      `INSERT INTO schema_version (version, description) VALUES (${migration.version}, '${migration.description.replace(/'/g, \"''\")}')`\n    );\n\n    console.info(`[SystemDB] Migration v${migration.version} applied`);\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/aiConfigRepository.ts",
    "content": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { encrypt, decrypt } from \"../crypto\";\nimport { fallbackPut, fallbackGetAll, fallbackGet } from \"../fallback\";\n\nexport interface AIProviderConfig {\n  profile_id: string;\n  provider: string;\n  config: string; // JSON: modelId, baseUrl (non-sensitive)\n  encrypted_api_key: string | null;\n}\n\nexport interface AIConversation {\n  id: string;\n  profile_id: string;\n  title: string | null;\n  messages: string; // JSON array\n  provider: string | null;\n  created_at: string;\n  updated_at: string;\n}\n\nexport async function saveProviderConfig(\n  profileId: string,\n  provider: string,\n  config: Record<string, unknown>,\n  apiKey: string | null,\n  cryptoKey: CryptoKey | null\n): Promise<void> {\n  const configJson = JSON.stringify(config);\n  let encryptedKey: string | null = null;\n\n  if (apiKey && cryptoKey) {\n    encryptedKey = await encrypt(apiKey, cryptoKey);\n  }\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT OR REPLACE INTO ai_provider_configs (profile_id, provider, config, encrypted_api_key)\n      VALUES (${sqlQuote(profileId)}, ${sqlQuote(provider)}, ${sqlQuote(configJson)}, ${encryptedKey ? sqlQuote(encryptedKey) : \"NULL\"})\n    `);\n  } else {\n    await fallbackPut(\"ai_provider_configs\", {\n      profile_id: profileId,\n      provider,\n      config: configJson,\n      encrypted_api_key: encryptedKey,\n    });\n  }\n}\n\nexport async function getProviderConfigs(\n  profileId: string,\n  cryptoKey: CryptoKey | null\n): Promise<Array<{ provider: string; config: Record<string, unknown>; apiKey: string | null }>> {\n  let records: AIProviderConfig[] = [];\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT * FROM ai_provider_configs WHERE profile_id = ${sqlQuote(profileId)}`\n    );\n    records = result.toArray().map((row) => {\n      const r = row.toJSON();\n      return {\n        profile_id: String(r.profile_id),\n        provider: String(r.provider),\n        config: String(r.config),\n        encrypted_api_key: r.encrypted_api_key ? String(r.encrypted_api_key) : null,\n      };\n    });\n  } else {\n    const all = (await fallbackGetAll(\"ai_provider_configs\")) as AIProviderConfig[];\n    records = all.filter((r) => r.profile_id === profileId);\n  }\n\n  return Promise.all(\n    records.map(async (r) => {\n      let apiKey: string | null = null;\n      if (r.encrypted_api_key && cryptoKey) {\n        try {\n          apiKey = await decrypt(r.encrypted_api_key, cryptoKey);\n        } catch {\n          console.warn(`Failed to decrypt API key for provider ${r.provider}`);\n        }\n      }\n      return {\n        provider: r.provider,\n        config: JSON.parse(r.config),\n        apiKey,\n      };\n    })\n  );\n}\n\nexport async function saveConversation(\n  profileId: string,\n  messages: unknown[],\n  options?: { id?: string; title?: string; provider?: string }\n): Promise<string> {\n  const id = options?.id ?? generateUUID();\n  const now = new Date().toISOString();\n  const messagesJson = JSON.stringify(messages);\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    // Check if exists\n    const existing = await conn.query(`SELECT id FROM ai_conversations WHERE id = ${sqlQuote(id)}`);\n    if (existing.toArray().length > 0) {\n      await conn.query(`\n        UPDATE ai_conversations SET messages = ${sqlQuote(messagesJson)}, title = ${options?.title ? sqlQuote(options.title) : \"NULL\"}, updated_at = ${sqlQuote(now)}\n        WHERE id = ${sqlQuote(id)}\n      `);\n    } else {\n      await conn.query(`\n        INSERT INTO ai_conversations (id, profile_id, title, messages, provider, created_at, updated_at)\n        VALUES (${sqlQuote(id)}, ${sqlQuote(profileId)}, ${options?.title ? sqlQuote(options.title) : \"NULL\"}, ${sqlQuote(messagesJson)}, ${options?.provider ? sqlQuote(options.provider) : \"NULL\"}, ${sqlQuote(now)}, ${sqlQuote(now)})\n      `);\n    }\n  } else {\n    const existing = (await fallbackGet(\"ai_conversations\", id)) as AIConversation | null;\n    await fallbackPut(\"ai_conversations\", {\n      id,\n      profile_id: profileId,\n      title: options?.title ?? existing?.title ?? null,\n      messages: messagesJson,\n      provider: options?.provider ?? existing?.provider ?? null,\n      created_at: existing?.created_at ?? now,\n      updated_at: now,\n    });\n  }\n\n  return id;\n}\n\nexport async function getConversations(profileId: string): Promise<AIConversation[]> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT * FROM ai_conversations WHERE profile_id = ${sqlQuote(profileId)} ORDER BY updated_at DESC`\n    );\n    return result.toArray().map((row) => {\n      const r = row.toJSON();\n      return {\n        id: String(r.id),\n        profile_id: String(r.profile_id),\n        title: r.title ? String(r.title) : null,\n        messages: String(r.messages),\n        provider: r.provider ? String(r.provider) : null,\n        created_at: String(r.created_at),\n        updated_at: String(r.updated_at),\n      };\n    });\n  } else {\n    const all = (await fallbackGetAll(\"ai_conversations\")) as AIConversation[];\n    return all\n      .filter((r) => r.profile_id === profileId)\n      .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/connectionRepository.ts",
    "content": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { encrypt, decrypt } from \"../crypto\";\nimport { fallbackPut, fallbackGetAll, fallbackDelete } from \"../fallback\";\n\nexport interface SavedConnection {\n  id: string;\n  profile_id: string;\n  name: string;\n  scope: string;\n  config: string; // JSON\n  encrypted_credentials: string | null;\n  environment: string;\n  created_at: string;\n}\n\nexport interface ConnectionInput {\n  name: string;\n  scope: string;\n  config: Record<string, unknown>;\n  credentials?: Record<string, unknown>;\n  environment?: string;\n}\n\nexport async function saveConnection(\n  profileId: string,\n  input: ConnectionInput,\n  cryptoKey: CryptoKey | null\n): Promise<SavedConnection> {\n  const id = generateUUID();\n  const now = new Date().toISOString();\n  const configJson = JSON.stringify(input.config);\n  let encryptedCreds: string | null = null;\n\n  if (input.credentials && cryptoKey) {\n    encryptedCreds = await encrypt(JSON.stringify(input.credentials), cryptoKey);\n  }\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT INTO connections (id, profile_id, name, scope, config, encrypted_credentials, environment, created_at)\n      VALUES (${sqlQuote(id)}, ${sqlQuote(profileId)}, ${sqlQuote(input.name)}, ${sqlQuote(input.scope)}, ${sqlQuote(configJson)}, ${encryptedCreds ? sqlQuote(encryptedCreds) : \"NULL\"}, ${sqlQuote(input.environment ?? \"APP\")}, ${sqlQuote(now)})\n    `);\n  } else {\n    await fallbackPut(\"connections\", {\n      id,\n      profile_id: profileId,\n      name: input.name,\n      scope: input.scope,\n      config: configJson,\n      encrypted_credentials: encryptedCreds,\n      environment: input.environment ?? \"APP\",\n      created_at: now,\n    });\n  }\n\n  return {\n    id,\n    profile_id: profileId,\n    name: input.name,\n    scope: input.scope,\n    config: configJson,\n    encrypted_credentials: encryptedCreds,\n    environment: input.environment ?? \"APP\",\n    created_at: now,\n  };\n}\n\nexport async function getConnections(\n  profileId: string,\n  cryptoKey: CryptoKey | null\n): Promise<\n  Array<{\n    id: string;\n    name: string;\n    scope: string;\n    config: Record<string, unknown>;\n    credentials: Record<string, unknown> | null;\n    environment: string;\n    created_at: string;\n  }>\n> {\n  let records: SavedConnection[] = [];\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT * FROM connections WHERE profile_id = ${sqlQuote(profileId)} ORDER BY created_at`\n    );\n    records = result.toArray().map((row) => {\n      const r = row.toJSON();\n      return {\n        id: String(r.id),\n        profile_id: String(r.profile_id),\n        name: String(r.name),\n        scope: String(r.scope),\n        config: String(r.config),\n        encrypted_credentials: r.encrypted_credentials ? String(r.encrypted_credentials) : null,\n        environment: String(r.environment),\n        created_at: String(r.created_at),\n      };\n    });\n  } else {\n    const all = (await fallbackGetAll(\"connections\")) as SavedConnection[];\n    records = all.filter((r) => r.profile_id === profileId);\n  }\n\n  return Promise.all(\n    records.map(async (r) => {\n      let credentials: Record<string, unknown> | null = null;\n      if (r.encrypted_credentials && cryptoKey) {\n        try {\n          credentials = JSON.parse(await decrypt(r.encrypted_credentials, cryptoKey));\n        } catch {\n          console.warn(`Failed to decrypt credentials for connection ${r.id}`);\n        }\n      }\n      return {\n        id: r.id,\n        name: r.name,\n        scope: r.scope,\n        config: JSON.parse(r.config),\n        credentials,\n        environment: r.environment,\n        created_at: r.created_at,\n      };\n    })\n  );\n}\n\nexport async function deleteConnection(id: string): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`DELETE FROM connections WHERE id = ${sqlQuote(id)}`);\n  } else {\n    await fallbackDelete(\"connections\", id);\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/index.ts",
    "content": "export * from \"./profileRepository\";\nexport * from \"./settingsRepository\";\nexport * from \"./connectionRepository\";\nexport * from \"./queryHistoryRepository\";\nexport * from \"./workspaceRepository\";\nexport * from \"./aiConfigRepository\";\nexport * from \"./savedQueryRepository\";\n"
  },
  {
    "path": "src/services/persistence/repositories/profileRepository.ts",
    "content": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGet, fallbackGetAll, fallbackDelete } from \"../fallback\";\n\nexport interface Profile {\n  id: string;\n  name: string;\n  avatar_emoji: string;\n  has_password: boolean;\n  password_verify_token: string | null;\n  created_at: string;\n  last_active: string;\n}\n\nexport async function createProfile(\n  name: string,\n  avatarEmoji = \"logo\",\n  hasPassword = false,\n  passwordVerifyToken: string | null = null\n): Promise<Profile> {\n  const id = generateUUID();\n  const now = new Date().toISOString();\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT INTO profiles (id, name, avatar_emoji, has_password, password_verify_token, created_at, last_active)\n      VALUES (${sqlQuote(id)}, ${sqlQuote(name)}, ${sqlQuote(avatarEmoji)}, ${hasPassword}, ${passwordVerifyToken ? sqlQuote(passwordVerifyToken) : \"NULL\"}, ${sqlQuote(now)}, ${sqlQuote(now)})\n    `);\n  } else {\n    await fallbackPut(\"profiles\", {\n      id,\n      name,\n      avatar_emoji: avatarEmoji,\n      has_password: hasPassword,\n      password_verify_token: passwordVerifyToken,\n      created_at: now,\n      last_active: now,\n    });\n  }\n\n  return {\n    id,\n    name,\n    avatar_emoji: avatarEmoji,\n    has_password: hasPassword,\n    password_verify_token: passwordVerifyToken,\n    created_at: now,\n    last_active: now,\n  };\n}\n\nexport async function getProfile(id: string): Promise<Profile | null> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(`SELECT * FROM profiles WHERE id = ${sqlQuote(id)}`);\n    const rows = result.toArray();\n    if (rows.length === 0) return null;\n    const row = rows[0].toJSON();\n    return {\n      id: row.id,\n      name: row.name,\n      avatar_emoji: row.avatar_emoji,\n      has_password: Boolean(row.has_password),\n      password_verify_token: row.password_verify_token ?? null,\n      created_at: String(row.created_at),\n      last_active: String(row.last_active),\n    };\n  } else {\n    const record = (await fallbackGet(\"profiles\", id)) as Profile | null;\n    return record;\n  }\n}\n\nexport async function listProfiles(): Promise<Profile[]> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(`SELECT * FROM profiles ORDER BY last_active DESC`);\n    return result.toArray().map((row) => {\n      const r = row.toJSON();\n      return {\n        id: r.id,\n        name: r.name,\n        avatar_emoji: r.avatar_emoji,\n        has_password: Boolean(r.has_password),\n        password_verify_token: r.password_verify_token ?? null,\n        created_at: String(r.created_at),\n        last_active: String(r.last_active),\n      };\n    });\n  } else {\n    const records = (await fallbackGetAll(\"profiles\")) as Profile[];\n    return records.sort(\n      (a, b) => new Date(b.last_active).getTime() - new Date(a.last_active).getTime()\n    );\n  }\n}\n\nexport async function updateProfile(\n  id: string,\n  updates: Partial<\n    Pick<Profile, \"name\" | \"avatar_emoji\" | \"has_password\" | \"password_verify_token\">\n  >\n): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const setClauses: string[] = [];\n    if (updates.name !== undefined) setClauses.push(`name = ${sqlQuote(updates.name)}`);\n    if (updates.avatar_emoji !== undefined)\n      setClauses.push(`avatar_emoji = ${sqlQuote(updates.avatar_emoji)}`);\n    if (updates.has_password !== undefined)\n      setClauses.push(`has_password = ${updates.has_password}`);\n    if (updates.password_verify_token !== undefined)\n      setClauses.push(\n        `password_verify_token = ${updates.password_verify_token ? sqlQuote(updates.password_verify_token) : \"NULL\"}`\n      );\n    if (setClauses.length > 0) {\n      await conn.query(`UPDATE profiles SET ${setClauses.join(\", \")} WHERE id = ${sqlQuote(id)}`);\n    }\n  } else {\n    const existing = (await fallbackGet(\"profiles\", id)) as Profile | null;\n    if (existing) {\n      await fallbackPut(\"profiles\", { ...existing, ...updates });\n    }\n  }\n}\n\nexport async function deleteProfile(id: string): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const qid = sqlQuote(id);\n    // Cascade delete all profile data in a transaction\n    await conn.query(`BEGIN TRANSACTION`);\n    try {\n      await conn.query(`DELETE FROM settings WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM connections WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM query_history WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM workspace_state WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM ai_provider_configs WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM ai_conversations WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM saved_queries WHERE profile_id = ${qid}`);\n      await conn.query(`DELETE FROM profiles WHERE id = ${qid}`);\n      await conn.query(`COMMIT`);\n    } catch (error) {\n      await conn.query(`ROLLBACK`);\n      throw error;\n    }\n  } else {\n    await fallbackDelete(\"profiles\", id);\n    // Fallback doesn't have indexes on profile_id, so cascade is skipped\n    // (would need a scan-and-delete approach for IndexedDB)\n  }\n}\n\nexport async function updateLastActive(id: string): Promise<void> {\n  const now = new Date().toISOString();\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(\n      `UPDATE profiles SET last_active = ${sqlQuote(now)} WHERE id = ${sqlQuote(id)}`\n    );\n  } else {\n    const existing = (await fallbackGet(\"profiles\", id)) as Profile | null;\n    if (existing) {\n      await fallbackPut(\"profiles\", { ...existing, last_active: now });\n    }\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/queryHistoryRepository.ts",
    "content": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGetAll, fallbackDelete } from \"../fallback\";\n\nexport interface HistoryEntry {\n  id: string;\n  profile_id: string;\n  connection_id: string | null;\n  sql_text: string;\n  error: string | null;\n  duration_ms: number | null;\n  row_count: number | null;\n  executed_at: string;\n}\n\nexport async function addHistoryEntry(\n  profileId: string,\n  sqlText: string,\n  options?: {\n    connectionId?: string;\n    error?: string;\n    durationMs?: number;\n    rowCount?: number;\n  }\n): Promise<HistoryEntry> {\n  const id = generateUUID();\n  const now = new Date().toISOString();\n\n  const entry: HistoryEntry = {\n    id,\n    profile_id: profileId,\n    connection_id: options?.connectionId ?? null,\n    sql_text: sqlText,\n    error: options?.error ?? null,\n    duration_ms: options?.durationMs ?? null,\n    row_count: options?.rowCount ?? null,\n    executed_at: now,\n  };\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT INTO query_history (id, profile_id, connection_id, sql_text, error, duration_ms, row_count, executed_at)\n      VALUES (${sqlQuote(id)}, ${sqlQuote(profileId)}, ${entry.connection_id ? sqlQuote(entry.connection_id) : \"NULL\"}, ${sqlQuote(sqlText)}, ${entry.error ? sqlQuote(entry.error) : \"NULL\"}, ${entry.duration_ms ?? \"NULL\"}, ${entry.row_count ?? \"NULL\"}, ${sqlQuote(now)})\n    `);\n  } else {\n    await fallbackPut(\"query_history\", { ...entry });\n  }\n\n  return entry;\n}\n\nexport async function getHistory(\n  profileId: string,\n  limit = 100,\n  offset = 0\n): Promise<HistoryEntry[]> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT * FROM query_history WHERE profile_id = ${sqlQuote(profileId)} ORDER BY executed_at DESC LIMIT ${limit} OFFSET ${offset}`\n    );\n    return result.toArray().map((row) => {\n      const r = row.toJSON();\n      return {\n        id: String(r.id),\n        profile_id: String(r.profile_id),\n        connection_id: r.connection_id ? String(r.connection_id) : null,\n        sql_text: String(r.sql_text),\n        error: r.error ? String(r.error) : null,\n        duration_ms: r.duration_ms != null ? Number(r.duration_ms) : null,\n        row_count: r.row_count != null ? Number(r.row_count) : null,\n        executed_at: String(r.executed_at),\n      };\n    });\n  } else {\n    const all = (await fallbackGetAll(\"query_history\")) as HistoryEntry[];\n    return all\n      .filter((r) => r.profile_id === profileId)\n      .sort((a, b) => new Date(b.executed_at).getTime() - new Date(a.executed_at).getTime())\n      .slice(offset, offset + limit);\n  }\n}\n\nexport async function clearHistory(profileId: string): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`DELETE FROM query_history WHERE profile_id = ${sqlQuote(profileId)}`);\n  } else {\n    // For fallback, only delete history entries belonging to this profile\n    const all = (await fallbackGetAll(\"query_history\")) as HistoryEntry[];\n    const toDelete = all.filter((item) => item.profile_id === profileId);\n    for (const item of toDelete) {\n      await fallbackDelete(\"query_history\", item.id);\n    }\n  }\n}\n\nexport async function getHistoryCount(profileId: string): Promise<number> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT COUNT(*) as cnt FROM query_history WHERE profile_id = ${sqlQuote(profileId)}`\n    );\n    return Number(result.toArray()[0]?.toJSON().cnt ?? 0);\n  } else {\n    const all = (await fallbackGetAll(\"query_history\")) as HistoryEntry[];\n    return all.filter((r) => r.profile_id === profileId).length;\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/savedQueryRepository.ts",
    "content": "import { generateUUID } from \"@/lib/utils\";\nimport { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGetAll, fallbackGet, fallbackDelete } from \"../fallback\";\n\nexport interface SavedQuery {\n  id: string;\n  profile_id: string;\n  name: string;\n  sql_text: string;\n  description: string | null;\n  tags: string | null; // JSON array\n  folder: string;\n  created_at: string;\n  updated_at: string;\n}\n\nexport async function saveQuery(\n  profileId: string,\n  input: { name: string; sqlText: string; description?: string; tags?: string[]; folder?: string }\n): Promise<SavedQuery> {\n  const id = generateUUID();\n  const now = new Date().toISOString();\n  const tagsJson = input.tags ? JSON.stringify(input.tags) : null;\n\n  const record: SavedQuery = {\n    id,\n    profile_id: profileId,\n    name: input.name,\n    sql_text: input.sqlText,\n    description: input.description ?? null,\n    tags: tagsJson,\n    folder: input.folder ?? \"default\",\n    created_at: now,\n    updated_at: now,\n  };\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT INTO saved_queries (id, profile_id, name, sql_text, description, tags, folder, created_at, updated_at)\n      VALUES (${sqlQuote(id)}, ${sqlQuote(profileId)}, ${sqlQuote(input.name)}, ${sqlQuote(input.sqlText)}, ${input.description ? sqlQuote(input.description) : \"NULL\"}, ${tagsJson ? sqlQuote(tagsJson) : \"NULL\"}, ${sqlQuote(record.folder)}, ${sqlQuote(now)}, ${sqlQuote(now)})\n    `);\n  } else {\n    await fallbackPut(\"saved_queries\", { ...record });\n  }\n\n  return record;\n}\n\nexport async function getSavedQueries(profileId: string, folder?: string): Promise<SavedQuery[]> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    let sql = `SELECT * FROM saved_queries WHERE profile_id = ${sqlQuote(profileId)}`;\n    if (folder) sql += ` AND folder = ${sqlQuote(folder)}`;\n    sql += ` ORDER BY updated_at DESC`;\n    const result = await conn.query(sql);\n    return result.toArray().map((row) => {\n      const r = row.toJSON();\n      return {\n        id: String(r.id),\n        profile_id: String(r.profile_id),\n        name: String(r.name),\n        sql_text: String(r.sql_text),\n        description: r.description ? String(r.description) : null,\n        tags: r.tags ? String(r.tags) : null,\n        folder: String(r.folder),\n        created_at: String(r.created_at),\n        updated_at: String(r.updated_at),\n      };\n    });\n  } else {\n    const all = (await fallbackGetAll(\"saved_queries\")) as SavedQuery[];\n    return all\n      .filter((r) => r.profile_id === profileId && (!folder || r.folder === folder))\n      .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());\n  }\n}\n\nexport async function updateSavedQuery(\n  id: string,\n  updates: Partial<Pick<SavedQuery, \"name\" | \"sql_text\" | \"description\" | \"tags\" | \"folder\">>\n): Promise<void> {\n  const now = new Date().toISOString();\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const setClauses: string[] = [`updated_at = ${sqlQuote(now)}`];\n    if (updates.name !== undefined) setClauses.push(`name = ${sqlQuote(updates.name)}`);\n    if (updates.sql_text !== undefined) setClauses.push(`sql_text = ${sqlQuote(updates.sql_text)}`);\n    if (updates.description !== undefined)\n      setClauses.push(\n        updates.description\n          ? `description = ${sqlQuote(updates.description)}`\n          : `description = NULL`\n      );\n    if (updates.tags !== undefined)\n      setClauses.push(updates.tags ? `tags = ${sqlQuote(updates.tags)}` : `tags = NULL`);\n    if (updates.folder !== undefined) setClauses.push(`folder = ${sqlQuote(updates.folder)}`);\n    await conn.query(\n      `UPDATE saved_queries SET ${setClauses.join(\", \")} WHERE id = ${sqlQuote(id)}`\n    );\n  } else {\n    const existing = (await fallbackGet(\"saved_queries\", id)) as SavedQuery | null;\n    if (existing) {\n      await fallbackPut(\"saved_queries\", { ...existing, ...updates, updated_at: now });\n    }\n  }\n}\n\nexport async function deleteSavedQuery(id: string): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`DELETE FROM saved_queries WHERE id = ${sqlQuote(id)}`);\n  } else {\n    await fallbackDelete(\"saved_queries\", id);\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/settingsRepository.ts",
    "content": "import { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGet, fallbackGetAll, fallbackDelete } from \"../fallback\";\n\nexport interface SettingRecord {\n  profile_id: string;\n  category: string;\n  key: string;\n  value: string; // JSON-encoded\n}\n\nexport async function getSetting(\n  profileId: string,\n  category: string,\n  key: string\n): Promise<string | null> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT value FROM settings WHERE profile_id = ${sqlQuote(profileId)} AND category = ${sqlQuote(category)} AND key = ${sqlQuote(key)}`\n    );\n    const rows = result.toArray();\n    return rows.length > 0 ? String(rows[0].toJSON().value) : null;\n  } else {\n    const record = (await fallbackGet(\"settings\", [\n      profileId,\n      category,\n      key,\n    ])) as SettingRecord | null;\n    return record?.value ?? null;\n  }\n}\n\nexport async function setSetting(\n  profileId: string,\n  category: string,\n  key: string,\n  value: string\n): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT OR REPLACE INTO settings (profile_id, category, key, value)\n      VALUES (${sqlQuote(profileId)}, ${sqlQuote(category)}, ${sqlQuote(key)}, ${sqlQuote(value)})\n    `);\n  } else {\n    await fallbackPut(\"settings\", { profile_id: profileId, category, key, value });\n  }\n}\n\nexport async function getSettingsByCategory(\n  profileId: string,\n  category: string\n): Promise<Record<string, string>> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT key, value FROM settings WHERE profile_id = ${sqlQuote(profileId)} AND category = ${sqlQuote(category)}`\n    );\n    const settings: Record<string, string> = {};\n    for (const row of result.toArray()) {\n      const r = row.toJSON();\n      settings[String(r.key)] = String(r.value);\n    }\n    return settings;\n  } else {\n    const all = (await fallbackGetAll(\"settings\")) as SettingRecord[];\n    const settings: Record<string, string> = {};\n    for (const rec of all) {\n      if (rec.profile_id === profileId && rec.category === category) {\n        settings[rec.key] = rec.value;\n      }\n    }\n    return settings;\n  }\n}\n\nexport async function deleteSetting(\n  profileId: string,\n  category: string,\n  key: string\n): Promise<void> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(\n      `DELETE FROM settings WHERE profile_id = ${sqlQuote(profileId)} AND category = ${sqlQuote(category)} AND key = ${sqlQuote(key)}`\n    );\n  } else {\n    await fallbackDelete(\"settings\", [profileId, category, key]);\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/repositories/workspaceRepository.ts",
    "content": "import { isUsingOpfs, getSystemConnection, sqlQuote } from \"../systemDb\";\nimport { fallbackPut, fallbackGet } from \"../fallback\";\n\nexport interface WorkspaceState {\n  profile_id: string;\n  tabs: string; // JSON array\n  active_tab_id: string | null;\n  current_connection_id: string | null;\n  current_database: string | null;\n  updated_at: string;\n}\n\nexport async function saveWorkspace(\n  profileId: string,\n  data: {\n    tabs: string;\n    activeTabId: string | null;\n    currentConnectionId: string | null;\n    currentDatabase: string | null;\n  }\n): Promise<void> {\n  const now = new Date().toISOString();\n\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    await conn.query(`\n      INSERT OR REPLACE INTO workspace_state (profile_id, tabs, active_tab_id, current_connection_id, current_database, updated_at)\n      VALUES (${sqlQuote(profileId)}, ${sqlQuote(data.tabs)}, ${data.activeTabId ? sqlQuote(data.activeTabId) : \"NULL\"}, ${data.currentConnectionId ? sqlQuote(data.currentConnectionId) : \"NULL\"}, ${data.currentDatabase ? sqlQuote(data.currentDatabase) : \"NULL\"}, ${sqlQuote(now)})\n    `);\n  } else {\n    await fallbackPut(\"workspace_state\", {\n      profile_id: profileId,\n      tabs: data.tabs,\n      active_tab_id: data.activeTabId,\n      current_connection_id: data.currentConnectionId,\n      current_database: data.currentDatabase,\n      updated_at: now,\n    });\n  }\n}\n\nexport async function loadWorkspace(profileId: string): Promise<WorkspaceState | null> {\n  if (isUsingOpfs()) {\n    const conn = getSystemConnection();\n    const result = await conn.query(\n      `SELECT * FROM workspace_state WHERE profile_id = ${sqlQuote(profileId)}`\n    );\n    const rows = result.toArray();\n    if (rows.length === 0) return null;\n    const r = rows[0].toJSON();\n    return {\n      profile_id: String(r.profile_id),\n      tabs: String(r.tabs),\n      active_tab_id: r.active_tab_id ? String(r.active_tab_id) : null,\n      current_connection_id: r.current_connection_id ? String(r.current_connection_id) : null,\n      current_database: r.current_database ? String(r.current_database) : null,\n      updated_at: String(r.updated_at),\n    };\n  } else {\n    return (await fallbackGet(\"workspace_state\", profileId)) as WorkspaceState | null;\n  }\n}\n"
  },
  {
    "path": "src/services/persistence/systemDb.ts",
    "content": "/**\n * System Database Service\n *\n * Manages persistence for profiles, settings, connections, query history,\n * and other app state using IndexedDB.\n *\n * Previously attempted OPFS with a dedicated DuckDB WASM instance, but\n * IndexedDB is more appropriate for lightweight metadata and avoids\n * creating a heavyweight second DuckDB instance (worker + 34MB WASM).\n */\n\nimport type { AsyncDuckDBConnection } from \"@duckdb/duckdb-wasm\";\n\n/** Escape a string value for safe use in SQL single-quoted literals */\nexport function sqlEscape(value: string): string {\n  return value.replace(/'/g, \"''\");\n}\n\n/** Escape and quote a string value for use in SQL WHERE clauses etc. Returns 'escaped_value' */\nexport function sqlQuote(value: string): string {\n  return `'${sqlEscape(value)}'`;\n}\n\n/** Escape and double-quote an identifier (table name, column name, etc.) */\nexport function sqlIdentifier(name: string): string {\n  return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n// Singleton state\nlet initialized = false;\nlet initPromise: Promise<void> | null = null;\n\n/**\n * Check whether OPFS is available in this browser.\n * Used by connection code to determine if OPFS connections are possible.\n */\nexport async function isOpfsAvailable(): Promise<boolean> {\n  try {\n    const root = await navigator.storage.getDirectory();\n    const testName = \".duck-ui-opfs-test\";\n    await root.getFileHandle(testName, { create: true });\n    await root.removeEntry(testName);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Initialize the system database. Sets up IndexedDB persistence.\n * Must be called before any repository functions.\n * Uses a Promise-based lock to prevent double-init races.\n */\nexport function initializeSystemDb(): Promise<void> {\n  if (!initPromise) {\n    initPromise = doInitialize();\n  }\n  return initPromise;\n}\n\nasync function doInitialize(): Promise<void> {\n  if (initialized) return;\n  console.info(\"[SystemDB] Using IndexedDB persistence\");\n  initialized = true;\n}\n\n/**\n * Get the system database connection. Throws since system DB now uses IndexedDB.\n */\nexport function getSystemConnection(): AsyncDuckDBConnection {\n  throw new Error(\"System database uses IndexedDB — no DuckDB connection available\");\n}\n\n/**\n * Check if the system DB is using OPFS (vs IndexedDB fallback).\n * Always returns false since system DB now exclusively uses IndexedDB.\n */\nexport function isUsingOpfs(): boolean {\n  return false;\n}\n\n/**\n * Check if system DB has been initialized.\n */\nexport function isSystemDbInitialized(): boolean {\n  return initialized;\n}\n\n/**\n * Close the system database connection. No-op since system DB uses IndexedDB.\n */\nexport async function closeSystemDb(): Promise<void> {\n  initialized = false;\n  initPromise = null;\n}\n"
  },
  {
    "path": "src/store/index.ts",
    "content": "import { create, type StateCreator } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport { createDuckdbSlice } from \"./slices/duckdbSlice\";\nimport { createConnectionSlice } from \"./slices/connectionSlice\";\nimport { createQuerySlice } from \"./slices/querySlice\";\nimport { createSchemaSlice } from \"./slices/schemaSlice\";\nimport { createTabSlice } from \"./slices/tabSlice\";\nimport { createDuckBrainSlice } from \"./slices/duckBrainSlice\";\nimport { createFileSystemSlice } from \"./slices/fileSystemSlice\";\nimport { createProfileSlice } from \"./slices/profileSlice\";\nimport { saveWorkspace } from \"@/services/persistence/repositories/workspaceRepository\";\nimport {\n  saveProviderConfig,\n  saveConversation,\n} from \"@/services/persistence/repositories/aiConfigRepository\";\nimport { isSystemDbInitialized } from \"@/services/persistence/systemDb\";\nimport type { DuckStoreState } from \"./types\";\n\n// Re-export all types from the centralized types file\nexport * from \"./types\";\n\n// Re-export cloud storage types for use in components\nexport type { CloudConnection, CloudSupportStatus } from \"@/lib/cloudStorage\";\n\nconst storeCreator: StateCreator<DuckStoreState, [[\"zustand/devtools\", never]]> = (...a) => ({\n  ...createDuckdbSlice(...a),\n  ...createConnectionSlice(...a),\n  ...createQuerySlice(...a),\n  ...createSchemaSlice(...a),\n  ...createTabSlice(...a),\n  ...createDuckBrainSlice(...a),\n  ...createFileSystemSlice(...a),\n  ...createProfileSlice(...a),\n});\n\nexport const useDuckStore = create<DuckStoreState>()(\n  devtools(storeCreator, { enabled: import.meta.env.DEV })\n);\n\n// ─── Auto-save: debounced writes to system DB ────────────────────────────────\n\nlet saveTimer: ReturnType<typeof setTimeout>;\nlet lastSavedTabs: string | undefined;\nlet lastSavedAiProvider: string | undefined;\nlet lastSavedMessages: number | undefined;\n\nasync function persistWorkspaceState(state: DuckStoreState): Promise<void> {\n  const { currentProfileId, encryptionKey, isProfileLoaded } = state;\n  if (!currentProfileId || !isProfileLoaded || !isSystemDbInitialized()) return;\n\n  try {\n    // Save workspace (tabs, active tab, current DB)\n    const tabsJson = JSON.stringify(\n      state.tabs.map((tab) => {\n        if (tab.type === \"notebook\" && typeof tab.content === \"string\") {\n          try {\n            const cells = JSON.parse(tab.content) as Array<Record<string, unknown>>;\n            const cleanCells = cells.map((c) => ({ ...c, result: undefined }));\n            return { ...tab, result: undefined, content: JSON.stringify(cleanCells) };\n          } catch {\n            return { ...tab, result: undefined };\n          }\n        }\n        return { ...tab, result: undefined };\n      })\n    );\n    if (tabsJson !== lastSavedTabs) {\n      await saveWorkspace(currentProfileId, {\n        tabs: tabsJson,\n        activeTabId: state.activeTabId,\n        currentConnectionId: state.currentConnection?.id ?? null,\n        currentDatabase: state.currentDatabase ?? null,\n      });\n      lastSavedTabs = tabsJson;\n    }\n\n    // Save AI provider configs when changed\n    const currentProvider = state.duckBrain.aiProvider;\n    if (currentProvider !== lastSavedAiProvider) {\n      const configs = state.duckBrain.providerConfigs;\n      for (const [provider, config] of Object.entries(configs)) {\n        if (config) {\n          const apiKey = \"apiKey\" in config ? (config.apiKey as string) : null;\n          const safeConfig: Record<string, unknown> = {\n            modelId: (config as Record<string, unknown>).modelId,\n          };\n          if (\"baseUrl\" in config) safeConfig.baseUrl = config.baseUrl;\n          await saveProviderConfig(\n            currentProfileId,\n            provider,\n            safeConfig,\n            apiKey ?? null,\n            encryptionKey\n          );\n        }\n      }\n      lastSavedAiProvider = currentProvider;\n    }\n\n    // Save AI messages when changed\n    const messageCount = state.duckBrain.messages.length;\n    if (messageCount !== lastSavedMessages && messageCount > 0) {\n      await saveConversation(currentProfileId, state.duckBrain.messages, {\n        id: `${currentProfileId}-default`,\n        title: \"Default conversation\",\n        provider: state.duckBrain.aiProvider,\n      });\n      lastSavedMessages = messageCount;\n    }\n  } catch (error) {\n    console.warn(\"[AutoSave] Failed to persist state:\", error);\n  }\n}\n\nlet autoSaveUnsubscribe: (() => void) | null = null;\n\nexport function startAutoSave(): void {\n  // Prevent duplicate subscriptions\n  if (autoSaveUnsubscribe) return;\n  autoSaveUnsubscribe = useDuckStore.subscribe((state) => {\n    if (!state.isProfileLoaded) return;\n    clearTimeout(saveTimer);\n    saveTimer = setTimeout(() => persistWorkspaceState(state), 2000);\n  });\n}\n\n// Also save on page unload\nif (typeof window !== \"undefined\") {\n  window.addEventListener(\"beforeunload\", () => {\n    const state = useDuckStore.getState();\n    if (state.isProfileLoaded) {\n      // Best-effort sync save (may be truncated by browser)\n      persistWorkspaceState(state);\n    }\n  });\n}\n"
  },
  {
    "path": "src/store/slices/connectionSlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport {\n  testExternalConnection,\n  testOPFSConnection,\n  cleanupOPFSConnection,\n} from \"@/services/duckdb\";\nimport type { DuckStoreState, ConnectionSlice } from \"../types\";\nimport {\n  saveConnection,\n  deleteConnection as deleteConnectionRepo,\n} from \"@/services/persistence/repositories/connectionRepository\";\n\nexport const createConnectionSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  ConnectionSlice\n> = (set, get) => ({\n  currentConnection: null,\n  connectionList: {\n    connections: [],\n  },\n  isLoadingExternalConnection: false,\n\n  addConnection: async (connection) => {\n    try {\n      set({ isLoadingExternalConnection: true, error: null });\n\n      if (get().connectionList.connections.find((c) => c.name === connection.name)) {\n        throw new Error(`A connection with the name \"${connection.name}\" already exists.`);\n      }\n\n      if (connection.scope === \"External\") {\n        await testExternalConnection(connection);\n      } else if (connection.scope === \"OPFS\") {\n        const { opfsDb, opfsConnection, currentConnection } = get();\n        if (opfsDb && opfsConnection) {\n          await cleanupOPFSConnection(opfsDb, opfsConnection, currentConnection?.path);\n        }\n        const opfsInstance = await testOPFSConnection(connection);\n        // Store the connection to avoid leaking handles and path locks\n        set({\n          db: opfsInstance.db,\n          connection: opfsInstance.connection,\n          opfsDb: opfsInstance.db,\n          opfsConnection: opfsInstance.connection,\n          currentConnection: {\n            environment: connection.environment,\n            id: connection.id,\n            name: connection.name,\n            scope: connection.scope,\n            path: connection.path,\n          },\n          currentDatabase: connection.path?.replace(/\\.db$/, \"\") || \"opfs\",\n        });\n      }\n\n      set((state) => ({\n        connectionList: {\n          connections: [...state.connectionList.connections, connection],\n        },\n      }));\n\n      // Persist to DB (fire-and-forget)\n      const { currentProfileId, encryptionKey } = get();\n      if (currentProfileId) {\n        const config: Record<string, unknown> = {\n          host: connection.host,\n          port: connection.port,\n          database: connection.database,\n          path: connection.path,\n          authMode: connection.authMode,\n        };\n        const credentials: Record<string, unknown> = {};\n        if (connection.password) credentials.password = connection.password;\n        if (connection.apiKey) credentials.apiKey = connection.apiKey;\n\n        saveConnection(\n          currentProfileId,\n          {\n            name: connection.name,\n            scope: connection.scope ?? \"External\",\n            config,\n            credentials: Object.keys(credentials).length > 0 ? credentials : undefined,\n            environment: connection.environment ?? \"APP\",\n          },\n          encryptionKey\n        ).catch((err) => console.warn(\"[Connection] Failed to persist:\", err));\n      }\n\n      toast.success(`Connection \"${connection.name}\" added successfully!`);\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n      set({ error: `Failed to add connection: ${errorMessage}` });\n      toast.error(`Failed to add connection: ${errorMessage}`);\n      throw error;\n    } finally {\n      set({ isLoadingExternalConnection: false });\n    }\n  },\n\n  updateConnection: (connection) => {\n    set((state) => ({\n      connectionList: {\n        connections: state.connectionList.connections.map((c) =>\n          c.id === connection.id ? connection : c\n        ),\n      },\n    }));\n  },\n\n  deleteConnection: (id) => {\n    set((state) => ({\n      connectionList: {\n        connections: state.connectionList.connections.filter((c) => c.id !== id),\n      },\n    }));\n    deleteConnectionRepo(id).catch((err) =>\n      console.warn(\"[Connection] Failed to delete from DB:\", err)\n    );\n  },\n\n  setCurrentConnection: async (connectionId) => {\n    try {\n      set({ isLoading: true });\n      const connectionProvider = get().connectionList.connections.find(\n        (c) => c.id === connectionId\n      );\n      if (!connectionProvider) {\n        throw new Error(`Connection with ID ${connectionId} not found.`);\n      }\n\n      const { wasmDb, wasmConnection, opfsDb, opfsConnection } = get();\n\n      if (connectionProvider.scope === \"WASM\") {\n        if (!wasmDb || !wasmConnection) {\n          throw new Error(\"WASM connection not initialized\");\n        }\n        set({\n          db: wasmDb,\n          connection: wasmConnection,\n          currentConnection: {\n            environment: connectionProvider.environment,\n            id: connectionProvider.id,\n            name: connectionProvider.name,\n            scope: connectionProvider.scope,\n          },\n          currentDatabase: \"memory\",\n        });\n      } else if (connectionProvider.scope === \"OPFS\") {\n        const needsNewConnection =\n          !opfsDb || !opfsConnection || connectionProvider.path !== get().currentConnection?.path;\n\n        if (needsNewConnection) {\n          toast.info(\"Initializing OPFS connection...\");\n\n          if (opfsDb && opfsConnection) {\n            await cleanupOPFSConnection(opfsDb, opfsConnection, get().currentConnection?.path);\n          }\n\n          const opfsInstance = await testOPFSConnection(connectionProvider);\n\n          set({\n            db: opfsInstance.db,\n            connection: opfsInstance.connection,\n            opfsDb: opfsInstance.db,\n            opfsConnection: opfsInstance.connection,\n            currentConnection: {\n              environment: connectionProvider.environment,\n              id: connectionProvider.id,\n              name: connectionProvider.name,\n              scope: connectionProvider.scope,\n              path: connectionProvider.path,\n            },\n            currentDatabase: connectionProvider.path?.replace(/\\.db$/, \"\") || \"opfs\",\n          });\n        } else {\n          set({\n            db: opfsDb,\n            connection: opfsConnection,\n            currentConnection: {\n              environment: connectionProvider.environment,\n              id: connectionProvider.id,\n              name: connectionProvider.name,\n              scope: connectionProvider.scope,\n              path: connectionProvider.path,\n            },\n            currentDatabase: connectionProvider.path?.replace(/\\.db$/, \"\") || \"opfs\",\n          });\n        }\n      } else if (connectionProvider.scope === \"External\") {\n        set({\n          db: null,\n          connection: null,\n          currentConnection: {\n            environment: connectionProvider.environment,\n            id: connectionProvider.id,\n            name: connectionProvider.name,\n            scope: connectionProvider.scope,\n            host: connectionProvider.host,\n            port: connectionProvider.port,\n            user: connectionProvider.user,\n            password: connectionProvider.password,\n            database: connectionProvider.database,\n            authMode: connectionProvider.authMode,\n            apiKey: connectionProvider.apiKey,\n          },\n          currentDatabase: connectionProvider.database || \"external\",\n        });\n      }\n\n      try {\n        await get().fetchDatabasesAndTablesInfo();\n      } catch {\n        // Schema fetch failure shouldn't prevent connection from completing\n      }\n      toast.success(`Connected to ${connectionProvider.name}`);\n    } catch (error) {\n      set({\n        error: `Failed to set current connection: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`,\n        isLoading: false,\n      });\n      toast.error(`Failed to connect: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n    } finally {\n      set({ isLoading: false });\n    }\n  },\n\n  getConnection: (connectionId) => {\n    return get().connectionList.connections.find((c) => c.id === connectionId);\n  },\n});\n"
  },
  {
    "path": "src/store/slices/duckBrainSlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport { generateUUID } from \"@/lib/utils\";\nimport { executeExternalQuery, resultToJSON, validateConnection } from \"@/services/duckdb\";\nimport type { DuckStoreState, DuckBrainSlice, DuckBrainMessage, QueryResult } from \"../types\";\n\n// Track the duckBrainService subscription to prevent leaks\nlet duckBrainServiceUnsubscribe: (() => void) | null = null;\n\nexport const createDuckBrainSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  DuckBrainSlice\n> = (set, get) => ({\n  duckBrain: {\n    modelStatus: \"idle\",\n    downloadProgress: 0,\n    downloadStatus: \"\",\n    isWebGPUSupported: null,\n    currentModel: null,\n    error: null,\n    messages: [],\n    isGenerating: false,\n    streamingContent: \"\",\n    isPanelOpen: false,\n    aiProvider: \"webllm\",\n    providerConfigs: {},\n  },\n\n  initializeDuckBrain: async (modelId) => {\n    const { duckBrainService } = await import(\"@/lib/duckBrain\");\n\n    // Unsubscribe from previous subscription to prevent leaks\n    if (duckBrainServiceUnsubscribe) {\n      duckBrainServiceUnsubscribe();\n    }\n    duckBrainServiceUnsubscribe = duckBrainService.subscribe((serviceState) => {\n      set((state) => ({\n        duckBrain: {\n          ...state.duckBrain,\n          modelStatus: serviceState.status,\n          downloadProgress: serviceState.downloadProgress,\n          downloadStatus: serviceState.downloadStatus,\n          isWebGPUSupported: serviceState.isWebGPUSupported,\n          currentModel: serviceState.currentModel,\n          error: serviceState.error,\n        },\n      }));\n    });\n\n    try {\n      await duckBrainService.initialize(modelId);\n    } catch (error) {\n      toast.error(\n        `Failed to initialize Duck Brain: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`\n      );\n    }\n  },\n\n  generateSQL: async (naturalLanguage) => {\n    const {\n      duckBrainService,\n      buildTextToSQLMessages,\n      formatSchemaForContext,\n      extractSQLFromResponse,\n    } = await import(\"@/lib/duckBrain\");\n\n    const { databases, duckBrain } = get();\n    const { aiProvider, providerConfigs } = duckBrain;\n    const isExternalProvider = aiProvider !== \"webllm\";\n\n    if (!isExternalProvider && duckBrain.modelStatus !== \"ready\") {\n      toast.error(\"Duck Brain is not ready. Please wait for the model to load.\");\n      return null;\n    }\n\n    if (isExternalProvider) {\n      if (aiProvider === \"openai-compatible\") {\n        const config = providerConfigs[\"openai-compatible\"];\n        if (!config?.baseUrl || !config?.modelId) {\n          toast.error(\"Please configure the Base URL and Model ID in Brain settings.\");\n          return null;\n        }\n      } else {\n        const config = providerConfigs[aiProvider as \"openai\" | \"anthropic\"];\n        if (!config?.apiKey) {\n          toast.error(`Please configure your ${aiProvider} API key in Brain settings.`);\n          return null;\n        }\n      }\n    }\n\n    const userMessage: DuckBrainMessage = {\n      id: generateUUID(),\n      role: \"user\",\n      content: naturalLanguage,\n      timestamp: new Date(),\n    };\n\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        messages: [...state.duckBrain.messages, userMessage],\n        isGenerating: true,\n        streamingContent: \"\",\n      },\n    }));\n\n    try {\n      const schemaContext = formatSchemaForContext(databases);\n      const messages = buildTextToSQLMessages(\n        naturalLanguage,\n        schemaContext.formatted,\n        duckBrain.messages,\n        true\n      );\n\n      let fullResponse = \"\";\n\n      if (isExternalProvider) {\n        const { createProvider } = await import(\"@/lib/duckBrain/providers\");\n        const config = providerConfigs[aiProvider as \"openai\" | \"anthropic\" | \"openai-compatible\"]!;\n        const provider = createProvider(aiProvider as \"openai\" | \"anthropic\" | \"openai-compatible\");\n\n        await provider.initialize({\n          apiKey: \"apiKey\" in config ? config.apiKey : undefined,\n          modelId: config.modelId,\n          baseUrl: \"baseUrl\" in config ? config.baseUrl : undefined,\n        });\n\n        await provider.generateStreaming(\n          messages,\n          {\n            onToken: (token) => {\n              fullResponse += token;\n              set((state) => ({\n                duckBrain: {\n                  ...state.duckBrain,\n                  streamingContent: fullResponse,\n                },\n              }));\n            },\n            onComplete: (finalText) => {\n              const parsed = extractSQLFromResponse(finalText);\n\n              const assistantMessage: DuckBrainMessage = {\n                id: generateUUID(),\n                role: \"assistant\",\n                content: finalText,\n                timestamp: new Date(),\n                sql: parsed.sql || undefined,\n              };\n\n              set((state) => ({\n                duckBrain: {\n                  ...state.duckBrain,\n                  messages: [...state.duckBrain.messages, assistantMessage],\n                  isGenerating: false,\n                  streamingContent: \"\",\n                },\n              }));\n            },\n            onError: (error) => {\n              set((state) => ({\n                duckBrain: {\n                  ...state.duckBrain,\n                  isGenerating: false,\n                  streamingContent: \"\",\n                  error: error.message,\n                },\n              }));\n              toast.error(`Generation failed: ${error.message}`);\n            },\n          },\n          { maxTokens: 512, temperature: 0.2 }\n        );\n\n        await provider.cleanup();\n      } else {\n        await duckBrainService.generateStreaming(\n          messages,\n          {\n            onToken: (_token, fullText) => {\n              fullResponse = fullText;\n              set((state) => ({\n                duckBrain: {\n                  ...state.duckBrain,\n                  streamingContent: fullText,\n                },\n              }));\n            },\n            onComplete: (finalText) => {\n              const parsed = extractSQLFromResponse(finalText);\n\n              const assistantMessage: DuckBrainMessage = {\n                id: generateUUID(),\n                role: \"assistant\",\n                content: finalText,\n                timestamp: new Date(),\n                sql: parsed.sql || undefined,\n              };\n\n              set((state) => ({\n                duckBrain: {\n                  ...state.duckBrain,\n                  messages: [...state.duckBrain.messages, assistantMessage],\n                  isGenerating: false,\n                  streamingContent: \"\",\n                },\n              }));\n            },\n            onError: (error) => {\n              set((state) => ({\n                duckBrain: {\n                  ...state.duckBrain,\n                  isGenerating: false,\n                  streamingContent: \"\",\n                  error: error.message,\n                },\n              }));\n              toast.error(`Generation failed: ${error.message}`);\n            },\n          },\n          { maxTokens: 512, temperature: 0.2 }\n        );\n      }\n\n      const parsed = extractSQLFromResponse(fullResponse);\n      return parsed.sql;\n    } catch (error) {\n      set((state) => ({\n        duckBrain: {\n          ...state.duckBrain,\n          isGenerating: false,\n          streamingContent: \"\",\n        },\n      }));\n      toast.error(\n        `Failed to generate SQL: ${error instanceof Error ? error.message : \"Unknown error\"}`\n      );\n      return null;\n    }\n  },\n\n  toggleBrainPanel: () => {\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        isPanelOpen: !state.duckBrain.isPanelOpen,\n      },\n    }));\n  },\n\n  abortGeneration: async () => {\n    const { duckBrainService } = await import(\"@/lib/duckBrain\");\n    duckBrainService.abort();\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        isGenerating: false,\n        streamingContent: \"\",\n      },\n    }));\n  },\n\n  clearBrainMessages: () => {\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        messages: [],\n      },\n    }));\n  },\n\n  addBrainMessage: (message) => {\n    const newMessage: DuckBrainMessage = {\n      ...message,\n      id: generateUUID(),\n      timestamp: new Date(),\n    };\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        messages: [...state.duckBrain.messages, newMessage],\n      },\n    }));\n  },\n\n  setStreamingContent: (content) => {\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        streamingContent: content,\n      },\n    }));\n  },\n\n  setIsGenerating: (isGenerating) => {\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        isGenerating,\n      },\n    }));\n  },\n\n  executeQueryInChat: async (messageId, sql) => {\n    const { currentConnection, connection, updateMessageQueryResult } = get();\n\n    updateMessageQueryResult(messageId, { status: \"running\" });\n\n    try {\n      let queryResult: QueryResult;\n\n      if (currentConnection?.scope === \"External\") {\n        queryResult = await executeExternalQuery(sql, currentConnection);\n      } else {\n        if (!connection) {\n          throw new Error(\"WASM connection not initialized\");\n        }\n        const wasmConnection = validateConnection(connection);\n        const result = await wasmConnection.query(sql);\n        queryResult = resultToJSON(result);\n      }\n\n      if (queryResult.error) {\n        updateMessageQueryResult(messageId, {\n          status: \"error\",\n          error: queryResult.error,\n        });\n        return null;\n      }\n\n      const serializeValue = (value: unknown): unknown => {\n        if (typeof value === \"bigint\") {\n          return Number(value);\n        }\n        if (Array.isArray(value)) {\n          return value.map(serializeValue);\n        }\n        if (value && typeof value === \"object\") {\n          const result: Record<string, unknown> = {};\n          for (const [k, v] of Object.entries(value)) {\n            result[k] = serializeValue(v);\n          }\n          return result;\n        }\n        return value;\n      };\n\n      const serializedResult = serializeValue(queryResult) as QueryResult;\n\n      updateMessageQueryResult(messageId, {\n        status: \"success\",\n        data: serializedResult,\n        executedAt: new Date(),\n      });\n\n      return serializedResult;\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : \"Query execution failed\";\n\n      updateMessageQueryResult(messageId, {\n        status: \"error\",\n        error: errorMessage,\n      });\n\n      return null;\n    }\n  },\n\n  updateMessageQueryResult: (messageId, queryResult) => {\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        messages: state.duckBrain.messages.map((m) =>\n          m.id === messageId ? { ...m, queryResult } : m\n        ),\n      },\n    }));\n  },\n\n  setAIProvider: (provider) => {\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        aiProvider: provider,\n        modelStatus: provider === \"webllm\" ? \"idle\" : \"ready\",\n        error: null,\n      },\n    }));\n  },\n\n  updateProviderConfig: (provider, config) => {\n    const providerConfig =\n      provider === \"openai-compatible\"\n        ? { baseUrl: config.baseUrl || \"\", modelId: config.modelId, apiKey: config.apiKey }\n        : { apiKey: config.apiKey || \"\", modelId: config.modelId };\n\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        providerConfigs: {\n          ...state.duckBrain.providerConfigs,\n          [provider]: providerConfig,\n        },\n      },\n    }));\n  },\n\n  initializeExternalProvider: async () => {\n    const { duckBrain } = get();\n    const { aiProvider, providerConfigs } = duckBrain;\n\n    if (aiProvider === \"webllm\") {\n      return;\n    }\n\n    const config = providerConfigs[aiProvider as \"openai\" | \"anthropic\" | \"openai-compatible\"];\n\n    if (aiProvider === \"openai-compatible\") {\n      if (!config || !(\"baseUrl\" in config) || !config.baseUrl || !config.modelId) {\n        toast.error(\"Please configure the Base URL and Model ID first\");\n        return;\n      }\n    } else {\n      if (!config || !(\"apiKey\" in config) || !config.apiKey) {\n        toast.error(`Please configure your ${aiProvider} API key first`);\n        return;\n      }\n    }\n\n    set((state) => ({\n      duckBrain: {\n        ...state.duckBrain,\n        modelStatus: \"loading\",\n        error: null,\n      },\n    }));\n\n    try {\n      const { createProvider } = await import(\"@/lib/duckBrain/providers\");\n      const provider = createProvider(aiProvider);\n      await provider.initialize({\n        apiKey: \"apiKey\" in config ? config.apiKey : undefined,\n        modelId: config.modelId,\n        baseUrl: \"baseUrl\" in config ? config.baseUrl : undefined,\n      });\n\n      set((state) => ({\n        duckBrain: {\n          ...state.duckBrain,\n          modelStatus: \"ready\",\n          currentModel: config.modelId,\n        },\n      }));\n\n      const providerName =\n        aiProvider === \"openai-compatible\" ? \"OpenAI-Compatible API\" : aiProvider;\n      toast.success(`${providerName} provider connected`);\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : \"Failed to connect\";\n      set((state) => ({\n        duckBrain: {\n          ...state.duckBrain,\n          modelStatus: \"error\",\n          error: errorMessage,\n        },\n      }));\n      toast.error(`Failed to connect: ${errorMessage}`);\n    }\n  },\n});\n"
  },
  {
    "path": "src/store/slices/duckdbSlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport { initializeWasmConnection } from \"@/services/duckdb\";\nimport type { DuckStoreState, DuckdbSlice, ConnectionProvider } from \"../types\";\nimport { getSetting } from \"@/services/persistence/repositories/settingsRepository\";\nimport { toast } from \"sonner\";\n\nexport const DEFAULT_DUCKDB_MEMORY_LIMIT_MB = 4096;\n\nexport const createDuckdbSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  DuckdbSlice\n> = (set, get) => ({\n  db: null,\n  connection: null,\n  wasmDb: null,\n  wasmConnection: null,\n  opfsDb: null,\n  opfsConnection: null,\n  isInitialized: false,\n  isLoading: false,\n  error: null,\n  currentDatabase: \"memory\",\n\n  initialize: async () => {\n    console.info(`[DuckDB] crossOriginIsolated: ${self.crossOriginIsolated}`);\n    const initialConnections: ConnectionProvider[] = [];\n\n    const {\n      DUCK_UI_EXTERNAL_CONNECTION_NAME: externalConnectionName = \"\",\n      DUCK_UI_EXTERNAL_HOST: externalHost = \"\",\n      DUCK_UI_EXTERNAL_PORT: externalPort = \"\",\n      DUCK_UI_EXTERNAL_USER: externalUser = \"\",\n      DUCK_UI_EXTERNAL_PASS: externalPass = \"\",\n      DUCK_UI_EXTERNAL_DATABASE_NAME: externalDatabaseName = \"\",\n    } = window.env || {};\n\n    const wasmConnection: ConnectionProvider = {\n      environment: \"APP\",\n      id: \"WASM\",\n      name: \"WASM\",\n      scope: \"WASM\",\n    };\n\n    initialConnections.push(wasmConnection);\n\n    if (externalConnectionName && externalHost && externalPort) {\n      initialConnections.push({\n        environment: \"ENV\",\n        id: externalConnectionName,\n        name: externalConnectionName,\n        scope: \"External\",\n        host: externalHost,\n        port: Number(externalPort),\n        user: externalUser,\n        password: externalPass,\n        database: externalDatabaseName,\n        authMode: \"password\",\n      });\n    }\n\n    set({\n      connectionList: { connections: initialConnections },\n    });\n\n    if (initialConnections.length > 0) {\n      const { db, connection } = await initializeWasmConnection();\n      set({\n        db,\n        connection,\n        wasmDb: db,\n        wasmConnection: connection,\n        isInitialized: true,\n        currentDatabase: \"memory\",\n      });\n      // Install extensions individually (non-blocking for offline support)\n      const failedExtensions: string[] = [];\n\n      try {\n        await connection.query(`SET enable_http_metadata_cache=true`);\n      } catch {\n        console.warn(\"[DuckDB] Failed to set enable_http_metadata_cache\");\n      }\n\n      try {\n        const profileId = get().currentProfileId;\n        let memoryLimitMb = DEFAULT_DUCKDB_MEMORY_LIMIT_MB;\n        if (profileId) {\n          const raw = await getSetting(profileId, \"duckdb\", \"memory_limit_mb\");\n          if (raw) {\n            const parsed = Number(JSON.parse(raw));\n            if (Number.isFinite(parsed) && parsed >= 256 && parsed <= 16384) {\n              memoryLimitMb = Math.floor(parsed);\n            }\n          }\n        }\n        await connection.query(`SET memory_limit='${memoryLimitMb}MB'`);\n      } catch {\n        console.warn(\"[DuckDB] Failed to set memory_limit\");\n      }\n\n      for (const ext of [\"arrow\", \"parquet\", \"ducklake\"]) {\n        try {\n          await connection.query(`INSTALL ${ext}`);\n          if (ext === \"ducklake\") {\n            await connection.query(`LOAD ${ext}`);\n          }\n        } catch {\n          console.warn(`[DuckDB] Failed to install ${ext} extension`);\n          failedExtensions.push(ext);\n        }\n      }\n\n      if (failedExtensions.length > 0) {\n        toast.warning(\n          `Some extensions failed to load (${failedExtensions.join(\", \")}). You may be offline — basic SQL features still work.`\n        );\n      }\n\n      if (initialConnections[0].scope !== \"WASM\") {\n        await get().setCurrentConnection(initialConnections[0].id);\n      } else {\n        set({\n          currentConnection: {\n            environment: initialConnections[0].environment,\n            id: initialConnections[0].id,\n            name: initialConnections[0].name,\n            scope: initialConnections[0].scope,\n          },\n        });\n        await get().fetchDatabasesAndTablesInfo();\n      }\n    } else {\n      set({ isLoading: false, isInitialized: true });\n    }\n  },\n\n  cleanup: async () => {\n    const { connection, db } = get();\n    try {\n      if (connection) await connection.close();\n      if (db) await db.terminate();\n    } finally {\n      set({\n        db: null,\n        connection: null,\n        isInitialized: false,\n        databases: [],\n        currentDatabase: \"memory\",\n        error: null,\n        queryHistory: [],\n        tabs: [\n          {\n            id: \"home\",\n            title: \"Home\",\n            type: \"home\",\n            content: \"\",\n          },\n        ],\n        activeTabId: \"home\",\n        currentConnection: null,\n      });\n    }\n  },\n});\n"
  },
  {
    "path": "src/store/slices/fileSystemSlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport { cloudStorageService } from \"@/lib/cloudStorage\";\nimport type { DuckStoreState, FileSystemSlice, MountedFolderInfo } from \"../types\";\n\nexport const createFileSystemSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  FileSystemSlice\n> = (set) => ({\n  mountedFolders: [],\n  isFileSystemSupported: typeof window !== \"undefined\" && \"showDirectoryPicker\" in window,\n  cloudConnections: [],\n  cloudSupportStatus: null,\n  isCloudStorageInitialized: false,\n\n  // File System Access Actions\n  initFileSystem: async () => {\n    const { fileSystemService, isFileSystemAccessSupported } = await import(\"@/lib/fileSystem\");\n\n    if (!isFileSystemAccessSupported()) {\n      set({ isFileSystemSupported: false });\n      return;\n    }\n\n    try {\n      await fileSystemService.init();\n\n      const folders = fileSystemService.getMountedFolders();\n      const folderInfos: MountedFolderInfo[] = folders.map((f) => ({\n        id: f.id,\n        name: f.name,\n        addedAt: f.addedAt,\n        hasPermission: f.hasPermission,\n      }));\n\n      set({ mountedFolders: folderInfos, isFileSystemSupported: true });\n    } catch (error) {\n      console.error(\"Failed to initialize file system:\", error);\n      toast.error(\"Failed to initialize file system access\");\n    }\n  },\n\n  mountFolder: async () => {\n    const { fileSystemService, isFileSystemAccessSupported } = await import(\"@/lib/fileSystem\");\n\n    if (!isFileSystemAccessSupported()) {\n      toast.error(\"File System Access API is not supported in this browser\");\n      return null;\n    }\n\n    try {\n      await fileSystemService.init();\n      const folder = await fileSystemService.mountFolder();\n\n      const folderInfo: MountedFolderInfo = {\n        id: folder.id,\n        name: folder.name,\n        addedAt: folder.addedAt,\n        hasPermission: folder.hasPermission,\n      };\n\n      set((state) => ({\n        mountedFolders: [...state.mountedFolders, folderInfo],\n      }));\n\n      toast.success(`Folder \"${folder.name}\" mounted successfully`);\n      return folderInfo;\n    } catch (error) {\n      if (error instanceof Error && error.name === \"AbortError\") {\n        return null;\n      }\n      console.error(\"Failed to mount folder:\", error);\n      toast.error(\"Failed to mount folder\");\n      return null;\n    }\n  },\n\n  unmountFolder: async (id) => {\n    const { fileSystemService } = await import(\"@/lib/fileSystem\");\n\n    try {\n      await fileSystemService.unmountFolder(id);\n\n      set((state) => ({\n        mountedFolders: state.mountedFolders.filter((f) => f.id !== id),\n      }));\n\n      toast.success(\"Folder unmounted\");\n    } catch (error) {\n      console.error(\"Failed to unmount folder:\", error);\n      toast.error(\"Failed to unmount folder\");\n    }\n  },\n\n  refreshFolderPermissions: async () => {\n    const { fileSystemService } = await import(\"@/lib/fileSystem\");\n\n    try {\n      await fileSystemService.init();\n      const permissions = await fileSystemService.checkAllPermissions();\n\n      set((state) => ({\n        mountedFolders: state.mountedFolders.map((f) => ({\n          ...f,\n          hasPermission: permissions.get(f.id) ?? false,\n        })),\n      }));\n    } catch (error) {\n      console.error(\"Failed to refresh permissions:\", error);\n    }\n  },\n\n  // Cloud Storage Actions\n  initCloudStorage: async () => {\n    try {\n      await cloudStorageService.init();\n      const connections = cloudStorageService.getConnections();\n      const supportStatus = cloudStorageService.getSupportStatus();\n\n      set({\n        cloudConnections: connections,\n        cloudSupportStatus: supportStatus,\n        isCloudStorageInitialized: true,\n      });\n\n      if (supportStatus && !supportStatus.httpfsAvailable) {\n        console.warn(\"Cloud storage: httpfs not available in this browser\");\n      }\n    } catch (error) {\n      console.error(\"Failed to initialize cloud storage:\", error);\n    }\n  },\n\n  addCloudConnection: async (config) => {\n    try {\n      const conn = await cloudStorageService.addConnection(config);\n\n      set((state) => ({\n        cloudConnections: [...state.cloudConnections, conn],\n      }));\n\n      toast.success(`Cloud connection \"${conn.name}\" added`);\n      return conn;\n    } catch (error) {\n      console.error(\"Failed to add cloud connection:\", error);\n      toast.error(\"Failed to add cloud connection\");\n      return null;\n    }\n  },\n\n  removeCloudConnection: async (id) => {\n    try {\n      const conn = cloudStorageService.getConnection(id);\n      await cloudStorageService.removeConnection(id);\n\n      set((state) => ({\n        cloudConnections: state.cloudConnections.filter((c) => c.id !== id),\n      }));\n\n      toast.success(`Cloud connection \"${conn?.name || id}\" removed`);\n    } catch (error) {\n      console.error(\"Failed to remove cloud connection:\", error);\n      toast.error(\"Failed to remove cloud connection\");\n    }\n  },\n\n  connectCloudStorage: async (id) => {\n    try {\n      const success = await cloudStorageService.connect(id);\n\n      if (success) {\n        set((state) => ({\n          cloudConnections: state.cloudConnections.map((c) =>\n            c.id === id ? { ...c, isConnected: true, lastError: undefined } : c\n          ),\n        }));\n        toast.success(\"Connected to cloud storage\");\n      }\n\n      return success;\n    } catch (error) {\n      const errorMsg = error instanceof Error ? error.message : String(error);\n\n      set((state) => ({\n        cloudConnections: state.cloudConnections.map((c) =>\n          c.id === id ? { ...c, isConnected: false, lastError: errorMsg } : c\n        ),\n      }));\n\n      toast.error(`Failed to connect: ${errorMsg}`);\n      return false;\n    }\n  },\n\n  disconnectCloudStorage: async (id) => {\n    try {\n      await cloudStorageService.disconnect(id);\n\n      set((state) => ({\n        cloudConnections: state.cloudConnections.map((c) =>\n          c.id === id ? { ...c, isConnected: false } : c\n        ),\n      }));\n\n      toast.success(\"Disconnected from cloud storage\");\n    } catch (error) {\n      console.error(\"Failed to disconnect cloud storage:\", error);\n    }\n  },\n\n  testCloudConnection: async (id) => {\n    return cloudStorageService.testConnection(id);\n  },\n});\n"
  },
  {
    "path": "src/store/slices/profileSlice.ts",
    "content": "import { StateCreator } from \"zustand\";\nimport type { DuckStoreState, ProfileSlice, Profile } from \"../types\";\nimport {\n  createProfile as createProfileRepo,\n  getProfile as getProfileRepo,\n  listProfiles as listProfilesRepo,\n  updateProfile as updateProfileRepo,\n  deleteProfile as deleteProfileRepo,\n  updateLastActive,\n} from \"@/services/persistence/repositories/profileRepository\";\nimport { getConnections } from \"@/services/persistence/repositories/connectionRepository\";\nimport { getHistory } from \"@/services/persistence/repositories/queryHistoryRepository\";\nimport { loadWorkspace } from \"@/services/persistence/repositories/workspaceRepository\";\nimport {\n  getProviderConfigs,\n  getConversations,\n} from \"@/services/persistence/repositories/aiConfigRepository\";\nimport { getSettingsByCategory } from \"@/services/persistence/repositories/settingsRepository\";\nimport {\n  generateEncryptionKey,\n  deriveKeyFromPassword,\n  generateSalt,\n  encrypt,\n  decrypt,\n  storeKeyForProfile,\n  loadKeyForProfile,\n  deleteKeyForProfile,\n} from \"@/services/persistence/crypto\";\nimport type {\n  EditorTab,\n  ConnectionProvider,\n  QueryHistoryItem,\n  AIProviderType,\n  ProviderConfigs,\n  DuckBrainMessage,\n} from \"../types\";\n\nconst VERIFY_TOKEN_PLAINTEXT = \"duck-ui-profile-verify\";\n\nexport const createProfileSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  ProfileSlice\n> = (set, get) => ({\n  currentProfileId: null,\n  currentProfile: null,\n  profiles: [],\n  isProfileLoaded: false,\n  encryptionKey: null,\n  savedQueriesVersion: 0,\n\n  bumpSavedQueriesVersion: () => set({ savedQueriesVersion: get().savedQueriesVersion + 1 }),\n\n  createProfile: async (name, password, avatarEmoji) => {\n    let cryptoKey: CryptoKey;\n    let salt: Uint8Array | undefined;\n    let verifyToken: string | null = null;\n\n    if (password) {\n      salt = generateSalt();\n      cryptoKey = await deriveKeyFromPassword(password, salt);\n      verifyToken = await encrypt(VERIFY_TOKEN_PLAINTEXT, cryptoKey);\n    } else {\n      cryptoKey = await generateEncryptionKey();\n    }\n\n    const profile = await createProfileRepo(name, avatarEmoji || \"logo\", !!password, verifyToken);\n    await storeKeyForProfile(profile.id, cryptoKey, salt);\n\n    const profiles = await listProfilesRepo();\n    set({\n      profiles: profiles.map(mapProfile),\n    });\n\n    return profile.id;\n  },\n\n  loadProfile: async (profileId, password) => {\n    const profile = await getProfileRepo(profileId);\n    if (!profile) throw new Error(\"Profile not found\");\n\n    // Load or derive encryption key\n    let cryptoKey: CryptoKey;\n    const stored = await loadKeyForProfile(profileId);\n\n    if (profile.has_password && password) {\n      if (!stored?.salt) throw new Error(\"No salt found for password-protected profile\");\n      cryptoKey = await deriveKeyFromPassword(password, stored.salt);\n      // Verify password by decrypting the stored verify token\n      if (profile.password_verify_token) {\n        try {\n          const decrypted = await decrypt(profile.password_verify_token, cryptoKey);\n          if (decrypted !== VERIFY_TOKEN_PLAINTEXT) {\n            throw new Error(\"Incorrect password\");\n          }\n        } catch {\n          throw new Error(\"Incorrect password\");\n        }\n      }\n    } else if (stored?.key) {\n      cryptoKey = stored.key;\n    } else {\n      // No key stored, generate a new one\n      cryptoKey = await generateEncryptionKey();\n      await storeKeyForProfile(profileId, cryptoKey);\n    }\n\n    // Load workspace state\n    const workspace = await loadWorkspace(profileId);\n    if (workspace) {\n      try {\n        const tabs = JSON.parse(workspace.tabs) as EditorTab[];\n        set({\n          tabs: tabs.length > 0 ? tabs : [{ id: \"home\", title: \"Home\", type: \"home\", content: \"\" }],\n          activeTabId: workspace.active_tab_id ?? tabs[0]?.id ?? \"home\",\n          currentDatabase: workspace.current_database ?? \"memory\",\n        });\n      } catch {\n        // Invalid workspace JSON, use defaults\n      }\n    }\n\n    // Load connections\n    const savedConns = await getConnections(profileId, cryptoKey);\n    if (savedConns.length > 0) {\n      const connections: ConnectionProvider[] = savedConns.map((c) => ({\n        environment: (c.environment as \"APP\" | \"ENV\" | \"BUILT_IN\") ?? \"APP\",\n        id: c.id,\n        name: c.name,\n        scope: c.scope as \"WASM\" | \"External\" | \"OPFS\",\n        ...(c.config as Record<string, unknown>),\n        ...(c.credentials ?? {}),\n      }));\n\n      set({\n        connectionList: { connections },\n      });\n    }\n\n    // Load query history\n    const history = await getHistory(profileId, 100);\n    if (history.length > 0) {\n      const queryHistory: QueryHistoryItem[] = history.map((h) => ({\n        id: h.id,\n        query: h.sql_text,\n        timestamp: new Date(h.executed_at),\n        ...(h.error ? { error: h.error } : {}),\n      }));\n      set({ queryHistory });\n    }\n\n    // Load AI provider configs\n    const aiConfigs = await getProviderConfigs(profileId, cryptoKey);\n    if (aiConfigs.length > 0) {\n      const providerConfigs: ProviderConfigs = {};\n      let aiProvider: AIProviderType = \"webllm\";\n\n      for (const cfg of aiConfigs) {\n        const config = cfg.config as Record<string, string>;\n        if (cfg.provider === \"openai\") {\n          providerConfigs.openai = {\n            apiKey: cfg.apiKey ?? \"\",\n            modelId: config.modelId ?? \"gpt-4o-mini\",\n          };\n          if (cfg.apiKey) aiProvider = \"openai\";\n        } else if (cfg.provider === \"anthropic\") {\n          providerConfigs.anthropic = {\n            apiKey: cfg.apiKey ?? \"\",\n            modelId: config.modelId ?? \"claude-sonnet-4-5-20250929\",\n          };\n          if (cfg.apiKey) aiProvider = \"anthropic\";\n        } else if (cfg.provider === \"openai-compatible\") {\n          providerConfigs[\"openai-compatible\"] = {\n            baseUrl: config.baseUrl ?? \"\",\n            modelId: config.modelId ?? \"\",\n            apiKey: cfg.apiKey ?? undefined,\n          };\n          if (config.baseUrl) aiProvider = \"openai-compatible\";\n        }\n      }\n\n      set((state) => ({\n        duckBrain: {\n          ...state.duckBrain,\n          providerConfigs,\n          aiProvider,\n        },\n      }));\n    }\n\n    // Load AI conversations (most recent)\n    const conversations = await getConversations(profileId);\n    if (conversations.length > 0) {\n      try {\n        const messages = JSON.parse(conversations[0].messages);\n        if (Array.isArray(messages) && messages.length > 0) {\n          set((state) => ({\n            duckBrain: {\n              ...state.duckBrain,\n              messages: messages.map((m: Record<string, unknown>) => ({\n                id: String(m.id ?? \"\"),\n                role: (m.role as \"user\" | \"assistant\") ?? \"user\",\n                content: String(m.content ?? \"\"),\n                timestamp: new Date((m.timestamp as string) ?? Date.now()),\n                ...(m.sql ? { sql: String(m.sql) } : {}),\n                ...(m.queryResult\n                  ? { queryResult: m.queryResult as DuckBrainMessage[\"queryResult\"] }\n                  : {}),\n              })),\n            },\n          }));\n        }\n      } catch {\n        // Invalid messages JSON\n      }\n    }\n\n    // Load theme setting\n    const themeSettings = await getSettingsByCategory(profileId, \"theme\");\n    if (themeSettings.mode) {\n      try {\n        const theme = JSON.parse(themeSettings.mode);\n        localStorage.setItem(\"vite-ui-theme\", theme);\n      } catch {\n        // Invalid theme value\n      }\n    }\n\n    await updateLastActive(profileId);\n\n    const allProfiles = await listProfilesRepo();\n\n    set({\n      currentProfileId: profileId,\n      currentProfile: mapProfile(profile),\n      profiles: allProfiles.map(mapProfile),\n      isProfileLoaded: true,\n      encryptionKey: cryptoKey,\n    });\n  },\n\n  switchProfile: async (profileId, password) => {\n    set({\n      isProfileLoaded: false,\n      currentProfileId: null,\n      currentProfile: null,\n      encryptionKey: null,\n    });\n    await get().loadProfile(profileId, password);\n  },\n\n  deleteProfile: async (profileId) => {\n    await deleteProfileRepo(profileId);\n    await deleteKeyForProfile(profileId);\n\n    const profiles = await listProfilesRepo();\n    set({ profiles: profiles.map(mapProfile) });\n  },\n\n  updateProfile: async (updates) => {\n    const { currentProfileId } = get();\n    if (!currentProfileId) return;\n\n    await updateProfileRepo(currentProfileId, {\n      ...(updates.name !== undefined ? { name: updates.name } : {}),\n      ...(updates.avatarEmoji !== undefined ? { avatar_emoji: updates.avatarEmoji } : {}),\n    });\n\n    const profile = await getProfileRepo(currentProfileId);\n    if (profile) {\n      set({ currentProfile: mapProfile(profile) });\n    }\n\n    const profiles = await listProfilesRepo();\n    set({ profiles: profiles.map(mapProfile) });\n  },\n});\n\nfunction mapProfile(p: {\n  id: string;\n  name: string;\n  avatar_emoji: string;\n  has_password: boolean;\n  created_at: string;\n  last_active: string;\n}): Profile {\n  return {\n    id: p.id,\n    name: p.name,\n    avatarEmoji: p.avatar_emoji,\n    hasPassword: p.has_password,\n    createdAt: p.created_at,\n    lastActive: p.last_active,\n  };\n}\n"
  },
  {
    "path": "src/store/slices/querySlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport {\n  executeExternalQuery,\n  resultToJSON,\n  validateConnection,\n  updateHistory,\n} from \"@/services/duckdb\";\nimport type { DuckStoreState, QuerySlice, QueryResult } from \"../types\";\nimport {\n  addHistoryEntry,\n  clearHistory as clearHistoryRepo,\n} from \"@/services/persistence/repositories/queryHistoryRepository\";\n\nexport const createQuerySlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  QuerySlice\n> = (set, get) => ({\n  queryHistory: [],\n  isExecuting: false,\n  executingTabs: {},\n\n  executeQuery: async (query, tabId?) => {\n    const { currentConnection, connection } = get();\n    try {\n      set((state) => ({\n        isExecuting: true,\n        executingTabs: tabId ? { ...state.executingTabs, [tabId]: true } : state.executingTabs,\n        error: null,\n      }));\n      let queryResult: QueryResult;\n      if (currentConnection?.scope === \"External\") {\n        queryResult = await executeExternalQuery(query, currentConnection);\n      } else {\n        if (!connection) throw new Error(\"WASM connection not initialized\");\n        const wasmConnection = validateConnection(connection);\n        const result = await wasmConnection.query(query);\n        queryResult = resultToJSON(result);\n      }\n      // Update query history and update tab result if applicable.\n      set((state) => {\n        const newExecutingTabs = { ...state.executingTabs };\n        if (tabId) delete newExecutingTabs[tabId];\n        return {\n          queryHistory: updateHistory(state.queryHistory, query),\n          tabs: state.tabs.map((tab) => (tab.id === tabId ? { ...tab, result: queryResult } : tab)),\n          isExecuting: false,\n          executingTabs: newExecutingTabs,\n        };\n      });\n      // Persist to DB (fire-and-forget)\n      const { currentProfileId } = get();\n      if (currentProfileId) {\n        addHistoryEntry(currentProfileId, query).catch(() => {});\n      }\n      // If the query is DDL, refresh schema.\n      // Strip leading comments and whitespace before matching\n      const stripped = query.trim().replace(/^(--[^\\n]*\\n\\s*|\\/\\*[\\s\\S]*?\\*\\/\\s*)*/g, \"\");\n      if (/^(CREATE|ALTER|DROP|ATTACH|DETACH|INSTALL|LOAD)\\b/i.test(stripped)) {\n        await get().fetchDatabasesAndTablesInfo();\n      }\n      return tabId ? undefined : queryResult;\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n      const errorResult: QueryResult = {\n        columns: [],\n        columnTypes: [],\n        data: [],\n        rowCount: 0,\n        error: errorMessage,\n      };\n      set((state) => {\n        const newExecutingTabs = { ...state.executingTabs };\n        if (tabId) delete newExecutingTabs[tabId];\n        return {\n          queryHistory: updateHistory(state.queryHistory, query, errorMessage),\n          tabs: state.tabs.map((tab) => (tab.id === tabId ? { ...tab, result: errorResult } : tab)),\n          isExecuting: false,\n          executingTabs: newExecutingTabs,\n          error: errorMessage,\n        };\n      });\n      // Persist to DB (fire-and-forget)\n      const { currentProfileId } = get();\n      if (currentProfileId) {\n        addHistoryEntry(currentProfileId, query, { error: errorMessage }).catch(() => {});\n      }\n    }\n  },\n\n  clearHistory: () => {\n    const { currentProfileId } = get();\n    set({ queryHistory: [] });\n    if (currentProfileId) {\n      clearHistoryRepo(currentProfileId).catch(() => {});\n    }\n  },\n\n  exportParquet: async (query: string) => {\n    try {\n      const { connection, db, currentConnection } = get();\n      if (currentConnection?.scope === \"External\") {\n        throw new Error(\"Exporting to parquet is not supported for external connections.\");\n      }\n      if (!connection || !db) {\n        throw new Error(\"Database not initialized\");\n      }\n      const now = new Date().toISOString().split(\".\")[0].replace(/[:]/g, \"-\");\n      const fileName = `result-${now}.parquet`;\n      await connection.query(`COPY (${query}) TO '${fileName}' (FORMAT 'parquet')`);\n      const parquet_buffer = await db.copyFileToBuffer(fileName);\n      await db.dropFile(fileName);\n      const arrayBuffer = parquet_buffer.buffer.slice(0) as ArrayBuffer;\n      return new Blob([arrayBuffer], { type: \"application/parquet\" });\n    } catch (error) {\n      console.error(\"Failed to export to parquet:\", error);\n      throw new Error(\n        `Parquet export failed: ${error instanceof Error ? error.message : \"Unknown error\"}`\n      );\n    }\n  },\n});\n"
  },
  {
    "path": "src/store/slices/schemaSlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport {\n  executeExternalQuery,\n  resultToJSON,\n  validateConnection,\n  fetchExternalDatabases,\n  fetchWasmDatabases,\n} from \"@/services/duckdb\";\nimport { sqlEscapeIdentifier, sqlEscapeString } from \"@/lib/sqlSanitize\";\nimport type { DuckStoreState, SchemaSlice, DatabaseInfo, ColumnStats, QueryResult } from \"../types\";\n\nexport const createSchemaSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  SchemaSlice\n> = (set, get) => ({\n  databases: [],\n  isLoadingDbTablesFetch: true,\n  schemaFetchError: null,\n\n  fetchDatabasesAndTablesInfo: async () => {\n    const { currentConnection, connection } = get();\n    try {\n      set({ isLoadingDbTablesFetch: true, schemaFetchError: null });\n      let databases: DatabaseInfo[] = [];\n\n      if (currentConnection?.scope === \"External\") {\n        databases = await fetchExternalDatabases(currentConnection);\n      } else if (currentConnection?.scope === \"OPFS\" || currentConnection?.scope === \"WASM\") {\n        if (!connection) {\n          set({ databases: [], error: null });\n          return;\n        }\n        const wasmConnection = validateConnection(connection);\n        databases = await fetchWasmDatabases(wasmConnection);\n      }\n\n      set({ databases, schemaFetchError: null });\n    } catch (error) {\n      const errorMessage = `Failed to load schema: ${\n        error instanceof Error ? error.message : \"Unknown error\"\n      }`;\n      set({\n        schemaFetchError: errorMessage,\n      });\n    } finally {\n      set({ isLoadingDbTablesFetch: false });\n    }\n  },\n\n  fetchTableColumnStats: async (databaseName, tableName) => {\n    const { currentConnection, connection } = get();\n    const query =\n      databaseName === \"main\" || databaseName === \"memory\" || databaseName === \":memory:\"\n        ? `SUMMARIZE ${sqlEscapeIdentifier(tableName)}`\n        : `SUMMARIZE ${sqlEscapeIdentifier(databaseName)}.${sqlEscapeIdentifier(tableName)}`;\n\n    try {\n      let result: QueryResult;\n\n      if (currentConnection?.scope === \"External\" && currentConnection) {\n        result = await executeExternalQuery(query, currentConnection);\n      } else {\n        const wasmConnection = validateConnection(connection);\n        const wasmResult = await wasmConnection.query(query);\n        result = resultToJSON(wasmResult);\n      }\n\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      const columnStats: ColumnStats[] = result.data.map((row: any) => ({\n        column_name: row.column_name,\n        column_type: row.column_type,\n        min: row.min,\n        max: row.max,\n        approx_unique: row.approx_unique,\n        avg: row.avg,\n        std: row.std,\n        q25: row.q25,\n        q50: row.q50,\n        q75: row.q75,\n        count: row.count,\n        null_percentage: row.null_percentage,\n      }));\n\n      return columnStats;\n    } catch (error) {\n      console.error(\"Failed to fetch column stats:\", error);\n      toast.error(\"Failed to load column statistics\");\n      return [];\n    }\n  },\n\n  deleteTable: async (tableName, database = \"memory\") => {\n    try {\n      const { connection, currentConnection } = get();\n      if (currentConnection?.scope === \"External\") {\n        throw new Error(\"Table deletion is not supported for external connections.\");\n      }\n      const wasmConnection = validateConnection(connection);\n      set({ isLoading: true });\n      await wasmConnection.query(\n        `DROP TABLE IF EXISTS ${sqlEscapeIdentifier(database)}.${sqlEscapeIdentifier(tableName)}`\n      );\n      await get().fetchDatabasesAndTablesInfo();\n      set({ isLoading: false });\n    } catch (error) {\n      set({\n        isLoading: false,\n        error: `Failed to delete table: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`,\n      });\n      throw error;\n    }\n  },\n\n  importFile: async (\n    fileName,\n    fileContent,\n    tableName,\n    fileType,\n    database = \"memory\",\n    options = {}\n  ) => {\n    try {\n      const { db, connection, currentConnection } = get();\n\n      if (currentConnection?.scope === \"External\") {\n        throw new Error(\"File import is not supported for external connections.\");\n      }\n\n      if (!db || !connection) throw new Error(\"Database not initialized\");\n      const buffer = new Uint8Array(fileContent);\n      try {\n        await db.dropFile(fileName);\n      } catch {\n        /* file may not exist */\n      }\n      await db.registerFileBuffer(fileName, buffer);\n      // Handle DuckDB database files (.duckdb, .db, .ddb)\n      if (fileType === \"duckdb\" || fileType === \"db\" || fileType === \"ddb\") {\n        await connection.query(\n          `ATTACH DATABASE '${sqlEscapeString(fileName)}' AS ${sqlEscapeIdentifier(tableName)}`\n        );\n        await get().fetchDatabasesAndTablesInfo();\n        return;\n      }\n\n      const importMode = options.importMode || \"table\";\n      const createType = importMode === \"view\" ? \"VIEW\" : \"TABLE\";\n\n      if (fileType.toLowerCase() === \"csv\") {\n        const csvOptions = options.csv || {};\n        const headerOption = csvOptions.header !== undefined ? csvOptions.header : true;\n        const autoDetectOption = csvOptions.autoDetect !== undefined ? csvOptions.autoDetect : true;\n        const ignoreErrorsOption =\n          csvOptions.ignoreErrors !== undefined ? csvOptions.ignoreErrors : true;\n        const nullPaddingOption =\n          csvOptions.nullPadding !== undefined ? csvOptions.nullPadding : true;\n        const allVarcharOption =\n          csvOptions.allVarchar !== undefined ? csvOptions.allVarchar : false;\n        const delimiterOption = csvOptions.delimiter || \",\";\n\n        const optionsString = `\n          header=${headerOption},\n          auto_detect=${autoDetectOption},\n          all_varchar=${allVarcharOption},\n          ignore_errors=${ignoreErrorsOption},\n          null_padding=${nullPaddingOption},\n          delim='${sqlEscapeString(delimiterOption)}'\n        `;\n\n        await connection.query(`\n          CREATE OR REPLACE ${createType} ${sqlEscapeIdentifier(tableName)} AS\n          SELECT * FROM read_csv('${sqlEscapeString(fileName)}', ${optionsString})\n        `);\n      } else if (fileType.toLowerCase() === \"json\") {\n        await connection.query(`\n          CREATE OR REPLACE ${createType} ${sqlEscapeIdentifier(tableName)} AS\n          SELECT * FROM read_json('${sqlEscapeString(fileName)}', auto_detect=true, ignore_errors=true)\n        `);\n      } else {\n        await connection.query(`\n          CREATE OR REPLACE ${createType} ${sqlEscapeIdentifier(tableName)} AS\n          SELECT * FROM read_${fileType.toLowerCase()}('${sqlEscapeString(fileName)}')\n        `);\n      }\n      const verification = await connection.query(`\n        SELECT COUNT(*) AS count\n        FROM information_schema.tables\n        WHERE table_name = '${sqlEscapeString(tableName)}'\n          AND table_schema = '${sqlEscapeString(database)}'\n      `);\n      if (verification.toArray()[0][0] === 0) {\n        throw new Error(`${createType} creation verification failed`);\n      }\n      await get().fetchDatabasesAndTablesInfo();\n    } catch (error) {\n      await get().fetchDatabasesAndTablesInfo();\n      throw new Error(`Import failed: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n    }\n  },\n});\n"
  },
  {
    "path": "src/store/slices/tabSlice.ts",
    "content": "import type { StateCreator } from \"zustand\";\nimport { toast } from \"sonner\";\nimport { generateUUID } from \"@/lib/utils\";\nimport type { DuckStoreState, TabSlice, EditorTab, NotebookCell } from \"../types\";\n\nfunction parseNotebookCells(tab: EditorTab): NotebookCell[] {\n  if (tab.type !== \"notebook\" || typeof tab.content !== \"string\") return [];\n  try {\n    return JSON.parse(tab.content) as NotebookCell[];\n  } catch {\n    return [];\n  }\n}\n\nfunction serializeCells(cells: NotebookCell[]): string {\n  return JSON.stringify(cells, (_key, value) =>\n    typeof value === \"bigint\" ? value.toString() : value\n  );\n}\n\nfunction createDefaultCell(type: \"sql\" | \"markdown\" = \"sql\"): NotebookCell {\n  return { id: generateUUID(), type, content: \"\" };\n}\n\nfunction updateNotebookContent(\n  tabs: EditorTab[],\n  tabId: string,\n  updater: (cells: NotebookCell[]) => NotebookCell[]\n): EditorTab[] {\n  return tabs.map((tab) => {\n    if (tab.id !== tabId || tab.type !== \"notebook\") return tab;\n    const cells = parseNotebookCells(tab);\n    return { ...tab, content: serializeCells(updater(cells)) };\n  });\n}\n\nexport const createTabSlice: StateCreator<\n  DuckStoreState,\n  [[\"zustand/devtools\", never]],\n  [],\n  TabSlice\n> = (set, get) => ({\n  tabs: [\n    {\n      id: \"home\",\n      title: \"Home\",\n      type: \"home\",\n      content: \"\",\n    },\n  ],\n  activeTabId: \"home\",\n\n  createTab: (type = \"sql\", content = \"\", title) => {\n    const isNotebook = type === \"notebook\";\n    const defaultContent = isNotebook ? serializeCells([createDefaultCell(\"sql\")]) : content;\n    const defaultTitle = isNotebook ? \"Untitled Notebook\" : \"Untitled Query\";\n\n    const newTab: EditorTab = {\n      id: generateUUID(),\n      title: typeof title === \"string\" ? title : defaultTitle,\n      type,\n      content: defaultContent,\n    };\n    set((state) => ({\n      tabs: [...state.tabs, newTab],\n      activeTabId: newTab.id,\n    }));\n  },\n\n  closeTab: (tabId) => {\n    set((state) => {\n      const updatedTabs = state.tabs.filter((tab) => tab.id !== tabId);\n      let newActiveTabId = state.activeTabId;\n      if (updatedTabs.length === 0) {\n        const newTab: EditorTab = {\n          id: generateUUID(),\n          title: \"Query 1\",\n          type: \"sql\",\n          content: \"\",\n        };\n        return {\n          tabs: [newTab],\n          activeTabId: newTab.id,\n        };\n      }\n      if (state.activeTabId === tabId) {\n        newActiveTabId = updatedTabs[0]?.id || null;\n      }\n      return {\n        tabs: updatedTabs,\n        activeTabId: newActiveTabId,\n      };\n    });\n  },\n\n  setActiveTab: (tabId) => {\n    set({ activeTabId: tabId });\n  },\n\n  updateTabQuery: (tabId, query) => {\n    set((state) => ({\n      tabs: state.tabs.map((tab) =>\n        tab.id === tabId && tab.type === \"sql\" ? { ...tab, content: query } : tab\n      ),\n    }));\n  },\n\n  updateTabTitle: (tabId, title) => {\n    set((state) => ({\n      tabs: state.tabs.map((tab) => (tab.id === tabId ? { ...tab, title } : tab)),\n    }));\n  },\n\n  updateTabChartConfig: (tabId, chartConfig) => {\n    set((state) => ({\n      tabs: state.tabs.map((tab) => (tab.id === tabId ? { ...tab, chartConfig } : tab)),\n    }));\n  },\n\n  moveTab: (oldIndex, newIndex) => {\n    set((state) => {\n      const newTabs = [...state.tabs];\n      const [movedTab] = newTabs.splice(oldIndex, 1);\n      newTabs.splice(newIndex, 0, movedTab);\n      return { tabs: newTabs };\n    });\n  },\n\n  closeAllTabs: () => {\n    try {\n      set((state) => ({\n        tabs: state.tabs.filter((tab) => tab.type === \"home\"),\n        activeTabId: \"home\",\n      }));\n      toast.success(\"All tabs closed successfully!\");\n    } catch (error: unknown) {\n      toast.error(\n        `Failed to close tabs: ${error instanceof Error ? error.message : \"Unknown error\"}`\n      );\n    }\n  },\n\n  // Notebook cell operations\n\n  getNotebookCells: (tabId) => {\n    const tab = get().tabs.find((t) => t.id === tabId);\n    return tab ? parseNotebookCells(tab) : [];\n  },\n\n  addNotebookCell: (tabId, afterCellId, cellType = \"sql\") => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) => {\n        const newCell = createDefaultCell(cellType);\n        if (!afterCellId) return [...cells, newCell];\n        const idx = cells.findIndex((c) => c.id === afterCellId);\n        if (idx === -1) return [...cells, newCell];\n        const result = [...cells];\n        result.splice(idx + 1, 0, newCell);\n        return result;\n      }),\n    }));\n  },\n\n  removeNotebookCell: (tabId, cellId) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) => {\n        if (cells.length <= 1) return cells; // Keep at least one cell\n        return cells.filter((c) => c.id !== cellId);\n      }),\n    }));\n  },\n\n  updateNotebookCellContent: (tabId, cellId, content) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) =>\n        cells.map((c) => (c.id === cellId ? { ...c, content } : c))\n      ),\n    }));\n  },\n\n  updateNotebookCellResult: (tabId, cellId, result) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) =>\n        cells.map((c) => (c.id === cellId ? { ...c, result } : c))\n      ),\n    }));\n  },\n\n  updateNotebookCellChartConfig: (tabId, cellId, chartConfig) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) =>\n        cells.map((c) => (c.id === cellId ? { ...c, chartConfig } : c))\n      ),\n    }));\n  },\n\n  moveNotebookCell: (tabId, cellId, direction) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) => {\n        const idx = cells.findIndex((c) => c.id === cellId);\n        if (idx === -1) return cells;\n        const targetIdx = direction === \"up\" ? idx - 1 : idx + 1;\n        if (targetIdx < 0 || targetIdx >= cells.length) return cells;\n        const result = [...cells];\n        [result[idx], result[targetIdx]] = [result[targetIdx], result[idx]];\n        return result;\n      }),\n    }));\n  },\n\n  toggleNotebookCellCollapsed: (tabId, cellId) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) =>\n        cells.map((c) => (c.id === cellId ? { ...c, collapsed: !c.collapsed } : c))\n      ),\n    }));\n  },\n\n  toggleNotebookCellType: (tabId, cellId) => {\n    set((state) => ({\n      tabs: updateNotebookContent(state.tabs, tabId, (cells) =>\n        cells.map((c) =>\n          c.id === cellId\n            ? {\n                ...c,\n                type: c.type === \"sql\" ? \"markdown\" : \"sql\",\n                result: null,\n                chartConfig: undefined,\n              }\n            : c\n        )\n      ),\n    }));\n  },\n});\n"
  },
  {
    "path": "src/store/types.ts",
    "content": "import * as duckdb from \"@duckdb/duckdb-wasm\";\nimport type { CloudConnection, CloudSupportStatus } from \"@/lib/cloudStorage\";\n\n//\n// Global Window type augmentation\n//\n\ndeclare global {\n  interface Window {\n    env?: {\n      DUCK_UI_EXTERNAL_CONNECTION_NAME: string;\n      DUCK_UI_EXTERNAL_HOST: string;\n      DUCK_UI_EXTERNAL_PORT: string;\n      DUCK_UI_EXTERNAL_USER: string;\n      DUCK_UI_EXTERNAL_PASS: string;\n      DUCK_UI_EXTERNAL_DATABASE_NAME: string;\n      DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS: boolean;\n      DUCK_UI_DUCKDB_WASM_USE_CDN?: boolean;\n      DUCK_UI_DUCKDB_WASM_BASE_URL?: string;\n    };\n  }\n}\n\n//\n// Connection Types\n//\n\nexport interface CurrentConnection {\n  environment: \"APP\" | \"ENV\" | \"BUILT_IN\";\n  id: string;\n  name: string;\n  scope: \"WASM\" | \"External\" | \"OPFS\";\n  host?: string;\n  port?: number;\n  user?: string;\n  password?: string;\n  database?: string;\n  authMode?: \"none\" | \"password\" | \"api_key\";\n  apiKey?: string;\n  path?: string;\n}\n\nexport interface ConnectionProvider {\n  environment: \"APP\" | \"ENV\" | \"BUILT_IN\";\n  id: string;\n  name: string;\n  scope: \"WASM\" | \"External\" | \"OPFS\";\n  host?: string;\n  port?: number;\n  user?: string;\n  password?: string;\n  database?: string;\n  authMode?: \"none\" | \"password\" | \"api_key\";\n  apiKey?: string;\n  path?: string;\n}\n\nexport interface ConnectionList {\n  connections: ConnectionProvider[];\n}\n\n//\n// Database & Table Types\n//\n\nexport interface ColumnInfo {\n  name: string;\n  type: string;\n  nullable: boolean;\n}\n\nexport interface ColumnStats {\n  column_name: string;\n  column_type: string;\n  min: string | null;\n  max: string | null;\n  approx_unique: string | null;\n  avg: string | null;\n  std: string | null;\n  q25: string | null;\n  q50: string | null;\n  q75: string | null;\n  count: string;\n  null_percentage: string;\n}\n\nexport interface TableInfo {\n  name: string;\n  schema: string;\n  columns: ColumnInfo[];\n  rowCount: number;\n  createdAt: string;\n  columnStats?: ColumnStats[];\n}\n\nexport interface DatabaseInfo {\n  name: string;\n  tables: TableInfo[];\n}\n\n//\n// Query Types\n//\n\nexport interface QueryResult {\n  columns: string[];\n  columnTypes: string[];\n  data: Record<string, unknown>[];\n  rowCount: number;\n  error?: string;\n}\n\nexport interface QueryHistoryItem {\n  id: string;\n  query: string;\n  timestamp: Date;\n  error?: string;\n}\n\nexport interface QueryResultArtifact {\n  status: \"pending\" | \"running\" | \"success\" | \"error\";\n  data?: QueryResult;\n  error?: string;\n  executedAt?: Date;\n}\n\nexport interface ExternalQueryResponse {\n  meta: Array<{ name: string; type: string }>;\n  data: unknown[][];\n  rows?: number;\n}\n\n//\n// AI Provider Types\n//\n\nexport type AIProviderType = \"webllm\" | \"openai\" | \"anthropic\" | \"openai-compatible\";\n\nexport interface ProviderConfigs {\n  openai?: { apiKey: string; modelId: string };\n  anthropic?: { apiKey: string; modelId: string };\n  \"openai-compatible\"?: { baseUrl: string; modelId: string; apiKey?: string };\n}\n\nexport interface DuckBrainMessage {\n  id: string;\n  role: \"user\" | \"assistant\";\n  content: string;\n  timestamp: Date;\n  sql?: string;\n  queryResult?: QueryResultArtifact;\n}\n\n//\n// File System Types\n//\n\nexport interface MountedFolderInfo {\n  id: string;\n  name: string;\n  addedAt: Date;\n  hasPermission: boolean;\n}\n\n//\n// Editor & Chart Types\n//\n\nexport type EditorTabType = \"sql\" | \"notebook\" | \"home\" | \"brain\" | \"connections\" | \"settings\";\n\nexport interface NotebookCell {\n  id: string;\n  type: \"sql\" | \"markdown\";\n  content: string;\n  result?: QueryResult | null;\n  chartConfig?: ChartConfig;\n  collapsed?: boolean;\n}\n\nexport type ChartType =\n  | \"bar\"\n  | \"line\"\n  | \"pie\"\n  | \"area\"\n  | \"scatter\"\n  | \"combo\"\n  | \"stacked_bar\"\n  | \"grouped_bar\"\n  | \"stacked_area\"\n  | \"donut\"\n  | \"heatmap\"\n  | \"treemap\"\n  | \"funnel\"\n  | \"gauge\"\n  | \"box\"\n  | \"bubble\";\n\nexport type AggregationType = \"sum\" | \"avg\" | \"count\" | \"min\" | \"max\" | \"none\";\nexport type SortOrder = \"asc\" | \"desc\" | \"none\";\nexport type AxisScale = \"linear\" | \"log\";\n\nexport interface SeriesConfig {\n  column: string;\n  label?: string;\n  color?: string;\n  type?: \"bar\" | \"line\" | \"area\";\n  yAxisId?: \"left\" | \"right\";\n  aggregation?: AggregationType;\n}\n\nexport interface AxisConfig {\n  label?: string;\n  scale?: AxisScale;\n  min?: number;\n  max?: number;\n  format?: string;\n  showGrid?: boolean;\n  rotate?: number;\n}\n\nexport interface LegendConfig {\n  show?: boolean;\n  position?: \"top\" | \"bottom\" | \"left\" | \"right\";\n  align?: \"start\" | \"center\" | \"end\";\n}\n\nexport interface AnnotationConfig {\n  id: string;\n  type: \"line\" | \"text\" | \"box\";\n  value?: number;\n  text?: string;\n  x?: number;\n  y?: number;\n  color?: string;\n}\n\nexport interface DataTransform {\n  groupBy?: string;\n  aggregation?: AggregationType;\n  sortBy?: string;\n  sortOrder?: SortOrder;\n  limit?: number;\n  filter?: string;\n}\n\nexport interface ChartConfig {\n  type: ChartType;\n  xAxis: string;\n  xAxisConfig?: AxisConfig;\n  yAxis?: string;\n  yAxisConfig?: AxisConfig;\n  series?: SeriesConfig[];\n  colorBy?: string;\n  sizeBy?: string;\n  transform?: DataTransform;\n  colors?: string[];\n  legend?: LegendConfig;\n  showValues?: boolean;\n  showGrid?: boolean;\n  enableAnimations?: boolean;\n  annotations?: AnnotationConfig[];\n  stacked?: boolean;\n  smooth?: boolean;\n  innerRadius?: number;\n  title?: string;\n  subtitle?: string;\n}\n\nexport interface EditorTab {\n  id: string;\n  title: string;\n  type: EditorTabType;\n  content: string | { database?: string; table?: string };\n  result?: QueryResult | null;\n  chartConfig?: ChartConfig;\n}\n\n//\n// Slice Interfaces\n//\n\nexport interface DuckdbSlice {\n  db: duckdb.AsyncDuckDB | null;\n  connection: duckdb.AsyncDuckDBConnection | null;\n  wasmDb: duckdb.AsyncDuckDB | null;\n  wasmConnection: duckdb.AsyncDuckDBConnection | null;\n  opfsDb: duckdb.AsyncDuckDB | null;\n  opfsConnection: duckdb.AsyncDuckDBConnection | null;\n  isInitialized: boolean;\n  isLoading: boolean;\n  error: string | null;\n  currentDatabase: string;\n\n  initialize: () => Promise<void>;\n  cleanup: () => Promise<void>;\n}\n\nexport interface ConnectionSlice {\n  currentConnection: CurrentConnection | null;\n  connectionList: ConnectionList;\n  isLoadingExternalConnection: boolean;\n\n  addConnection: (connection: ConnectionProvider) => Promise<void>;\n  updateConnection: (connection: ConnectionProvider) => void;\n  deleteConnection: (id: string) => void;\n  setCurrentConnection: (connectionId: string) => Promise<void>;\n  getConnection: (connectionId: string) => ConnectionProvider | undefined;\n}\n\nexport interface QuerySlice {\n  queryHistory: QueryHistoryItem[];\n  isExecuting: boolean;\n  executingTabs: Record<string, boolean>;\n\n  executeQuery: (query: string, tabId?: string) => Promise<QueryResult | void>;\n  clearHistory: () => void;\n  exportParquet: (query: string) => Promise<Blob>;\n}\n\nexport interface SchemaSlice {\n  databases: DatabaseInfo[];\n  isLoadingDbTablesFetch: boolean;\n  schemaFetchError: string | null;\n\n  fetchDatabasesAndTablesInfo: () => Promise<void>;\n  fetchTableColumnStats: (databaseName: string, tableName: string) => Promise<ColumnStats[]>;\n  deleteTable: (tableName: string, database?: string) => Promise<void>;\n  importFile: (\n    fileName: string,\n    fileContent: ArrayBuffer,\n    tableName: string,\n    fileType: string,\n    database?: string,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    options?: Record<string, any>\n  ) => Promise<void>;\n}\n\nexport interface TabSlice {\n  tabs: EditorTab[];\n  activeTabId: string | null;\n\n  createTab: (type?: EditorTabType, title?: string, content?: EditorTab[\"content\"]) => void;\n  closeTab: (tabId: string) => void;\n  setActiveTab: (tabId: string) => void;\n  updateTabQuery: (tabId: string, query: string) => void;\n  updateTabTitle: (tabId: string, title: string) => void;\n  updateTabChartConfig: (tabId: string, chartConfig: ChartConfig | undefined) => void;\n  moveTab: (oldIndex: number, newIndex: number) => void;\n  closeAllTabs: () => void;\n\n  // Notebook cell operations\n  getNotebookCells: (tabId: string) => NotebookCell[];\n  addNotebookCell: (tabId: string, afterCellId?: string, cellType?: \"sql\" | \"markdown\") => void;\n  removeNotebookCell: (tabId: string, cellId: string) => void;\n  updateNotebookCellContent: (tabId: string, cellId: string, content: string) => void;\n  updateNotebookCellResult: (tabId: string, cellId: string, result: QueryResult | null) => void;\n  updateNotebookCellChartConfig: (\n    tabId: string,\n    cellId: string,\n    chartConfig: ChartConfig | undefined\n  ) => void;\n  moveNotebookCell: (tabId: string, cellId: string, direction: \"up\" | \"down\") => void;\n  toggleNotebookCellCollapsed: (tabId: string, cellId: string) => void;\n  toggleNotebookCellType: (tabId: string, cellId: string) => void;\n}\n\nexport interface DuckBrainSlice {\n  duckBrain: {\n    modelStatus: \"idle\" | \"checking\" | \"downloading\" | \"loading\" | \"ready\" | \"error\";\n    downloadProgress: number;\n    downloadStatus: string;\n    isWebGPUSupported: boolean | null;\n    currentModel: string | null;\n    error: string | null;\n    messages: DuckBrainMessage[];\n    isGenerating: boolean;\n    streamingContent: string;\n    isPanelOpen: boolean;\n    aiProvider: AIProviderType;\n    providerConfigs: ProviderConfigs;\n  };\n\n  initializeDuckBrain: (modelId?: string) => Promise<void>;\n  generateSQL: (naturalLanguage: string) => Promise<string | null>;\n  toggleBrainPanel: () => void;\n  abortGeneration: () => void;\n  clearBrainMessages: () => void;\n  addBrainMessage: (message: Omit<DuckBrainMessage, \"id\" | \"timestamp\">) => void;\n  setStreamingContent: (content: string) => void;\n  setIsGenerating: (isGenerating: boolean) => void;\n  executeQueryInChat: (messageId: string, sql: string) => Promise<QueryResult | null>;\n  updateMessageQueryResult: (messageId: string, queryResult: QueryResultArtifact) => void;\n  setAIProvider: (provider: AIProviderType) => void;\n  updateProviderConfig: (\n    provider: \"openai\" | \"anthropic\" | \"openai-compatible\",\n    config: { apiKey?: string; modelId: string; baseUrl?: string }\n  ) => void;\n  initializeExternalProvider: () => Promise<void>;\n}\n\nexport interface FileSystemSlice {\n  mountedFolders: MountedFolderInfo[];\n  isFileSystemSupported: boolean;\n  cloudConnections: CloudConnection[];\n  cloudSupportStatus: CloudSupportStatus | null;\n  isCloudStorageInitialized: boolean;\n\n  initFileSystem: () => Promise<void>;\n  mountFolder: () => Promise<MountedFolderInfo | null>;\n  unmountFolder: (id: string) => Promise<void>;\n  refreshFolderPermissions: () => Promise<void>;\n  initCloudStorage: () => Promise<void>;\n  addCloudConnection: (\n    config: Omit<CloudConnection, \"id\" | \"addedAt\" | \"isConnected\">\n  ) => Promise<CloudConnection | null>;\n  removeCloudConnection: (id: string) => Promise<void>;\n  connectCloudStorage: (id: string) => Promise<boolean>;\n  disconnectCloudStorage: (id: string) => Promise<void>;\n  testCloudConnection: (id: string) => Promise<{ success: boolean; error?: string }>;\n}\n\n//\n// Profile Types\n//\n\nexport interface Profile {\n  id: string;\n  name: string;\n  avatarEmoji: string;\n  hasPassword: boolean;\n  createdAt: string;\n  lastActive: string;\n}\n\nexport interface ProfileSlice {\n  currentProfileId: string | null;\n  currentProfile: Profile | null;\n  profiles: Profile[];\n  isProfileLoaded: boolean;\n  encryptionKey: CryptoKey | null;\n\n  loadProfile: (profileId: string, password?: string) => Promise<void>;\n  createProfile: (name: string, password?: string, avatarEmoji?: string) => Promise<string>;\n  deleteProfile: (profileId: string) => Promise<void>;\n  switchProfile: (profileId: string, password?: string) => Promise<void>;\n  updateProfile: (updates: Partial<Pick<Profile, \"name\" | \"avatarEmoji\">>) => Promise<void>;\n\n  savedQueriesVersion: number;\n  bumpSavedQueriesVersion: () => void;\n}\n\n//\n// Composed Store Type\n//\n\nexport type DuckStoreState = DuckdbSlice &\n  ConnectionSlice &\n  QuerySlice &\n  SchemaSlice &\n  TabSlice &\n  DuckBrainSlice &\n  FileSystemSlice &\n  ProfileSlice;\n"
  },
  {
    "path": "src/types/filesystem.d.ts",
    "content": "/**\n * File System Access API type declarations\n * These APIs are available in Chrome 86+ and Edge 86+\n */\n\ninterface FileSystemHandlePermissionDescriptor {\n  mode?: \"read\" | \"readwrite\";\n}\n\ninterface FileSystemHandle {\n  readonly kind: \"file\" | \"directory\";\n  readonly name: string;\n  isSameEntry(other: FileSystemHandle): Promise<boolean>;\n  queryPermission(descriptor?: FileSystemHandlePermissionDescriptor): Promise<PermissionState>;\n  requestPermission(descriptor?: FileSystemHandlePermissionDescriptor): Promise<PermissionState>;\n}\n\ninterface FileSystemFileHandle extends FileSystemHandle {\n  readonly kind: \"file\";\n  getFile(): Promise<File>;\n  createWritable(options?: FileSystemCreateWritableOptions): Promise<FileSystemWritableFileStream>;\n}\n\ninterface FileSystemDirectoryHandle extends FileSystemHandle {\n  readonly kind: \"directory\";\n  getDirectoryHandle(\n    name: string,\n    options?: FileSystemGetDirectoryOptions\n  ): Promise<FileSystemDirectoryHandle>;\n  getFileHandle(name: string, options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle>;\n  removeEntry(name: string, options?: FileSystemRemoveOptions): Promise<void>;\n  resolve(possibleDescendant: FileSystemHandle): Promise<string[] | null>;\n  keys(): AsyncIterableIterator<string>;\n  values(): AsyncIterableIterator<FileSystemDirectoryHandle | FileSystemFileHandle>;\n  entries(): AsyncIterableIterator<[string, FileSystemDirectoryHandle | FileSystemFileHandle]>;\n  [Symbol.asyncIterator](): AsyncIterableIterator<\n    [string, FileSystemDirectoryHandle | FileSystemFileHandle]\n  >;\n}\n\ninterface FileSystemGetDirectoryOptions {\n  create?: boolean;\n}\n\ninterface FileSystemGetFileOptions {\n  create?: boolean;\n}\n\ninterface FileSystemRemoveOptions {\n  recursive?: boolean;\n}\n\ninterface FileSystemCreateWritableOptions {\n  keepExistingData?: boolean;\n}\n\ninterface FileSystemWritableFileStream extends WritableStream {\n  write(data: FileSystemWriteChunkType): Promise<void>;\n  seek(position: number): Promise<void>;\n  truncate(size: number): Promise<void>;\n}\n\ntype FileSystemWriteChunkType = ArrayBuffer | ArrayBufferView | Blob | string | WriteParams;\n\ninterface WriteParams {\n  type: \"write\" | \"seek\" | \"truncate\";\n  size?: number;\n  position?: number;\n  data?: ArrayBuffer | ArrayBufferView | Blob | string;\n}\n\ninterface OpenFilePickerOptions {\n  multiple?: boolean;\n  excludeAcceptAllOption?: boolean;\n  types?: FilePickerAcceptType[];\n  startIn?: FileSystemHandle | WellKnownDirectory;\n}\n\ninterface SaveFilePickerOptions {\n  excludeAcceptAllOption?: boolean;\n  suggestedName?: string;\n  types?: FilePickerAcceptType[];\n  startIn?: FileSystemHandle | WellKnownDirectory;\n}\n\ninterface DirectoryPickerOptions {\n  id?: string;\n  mode?: \"read\" | \"readwrite\";\n  startIn?: FileSystemHandle | WellKnownDirectory;\n}\n\ninterface FilePickerAcceptType {\n  description?: string;\n  accept: Record<string, string | string[]>;\n}\n\ntype WellKnownDirectory = \"desktop\" | \"documents\" | \"downloads\" | \"music\" | \"pictures\" | \"videos\";\n\ninterface Window {\n  showOpenFilePicker(options?: OpenFilePickerOptions): Promise<FileSystemFileHandle[]>;\n  showSaveFilePicker(options?: SaveFilePickerOptions): Promise<FileSystemFileHandle>;\n  showDirectoryPicker(options?: DirectoryPickerOptions): Promise<FileSystemDirectoryHandle>;\n}\n"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare const __DUCK_UI_VERSION__: string;\ndeclare const __DUCK_UI_RELEASE_DATE__: string;\ndeclare const __DUCK_UI_BUILD_DUCKDB_CDN_ONLY__: boolean;\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"docs\", \"node_modules\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"files\": [],\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2023\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { defineConfig, loadEnv } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport path from 'path';\nimport pkg from './package.json';\nimport tailwindcss from '@tailwindcss/vite';\n\nexport default defineConfig(({ mode }) => {\n  // Only load DUCK_UI_ prefixed env vars to prevent leaking CI secrets\n  // (e.g., GITHUB_TOKEN, GHCR_PAT) into the JS bundle\n  const env = loadEnv(mode, process.cwd(), 'DUCK_UI_');\n  const buildDuckdbCdnOnly = env.DUCK_UI_DUCKDB_WASM_CDN_ONLY === 'true';\n\n  // Manually construct the object to be defined\n  // Filter out keys with invalid JS identifier characters (fixes Windows builds where\n  // env vars like \"=::\" exist). See: https://github.com/caioricciuti/duck-ui/issues/26\n  const processEnvValues: Record<string, string> = {};\n  for (const key in env) {\n    if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {\n      processEnvValues[`import.meta.env.${key}`] = JSON.stringify(env[key]);\n    }\n  }\n\n  return {\n    base: process.env.DUCK_UI_BASEPATH ?? '/',\n    plugins: [react(), tailwindcss()],\n    server: {\n      headers: {\n        'Cross-Origin-Opener-Policy': 'same-origin',\n        'Cross-Origin-Embedder-Policy': 'credentialless',\n      },\n    },\n    preview: {\n      headers: {\n        'Cross-Origin-Opener-Policy': 'same-origin',\n        'Cross-Origin-Embedder-Policy': 'credentialless',\n      },\n    },\n    resolve: {\n      alias: {\n        '@': path.resolve(__dirname, './src'),\n      },\n    },\n    define: {\n      __DUCK_UI_VERSION__: JSON.stringify(pkg.version),\n      __DUCK_UI_RELEASE_DATE__: JSON.stringify(pkg.release_date),\n      __DUCK_UI_BUILD_DUCKDB_CDN_ONLY__: JSON.stringify(buildDuckdbCdnOnly),\n      ...processEnvValues // Spread the processed variables\n    },\n  };\n});\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from \"vitest/config\";\nimport path from \"path\";\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      \"@\": path.resolve(__dirname, \"./src\"),\n    },\n  },\n  test: {\n    globals: true,\n    environment: \"node\",\n    include: [\"src/**/*.test.ts\"],\n  },\n});\n"
  }
]