# AGENTS.md Guidance for AI coding agents working on **Gridline**. --- ## Project Identity Gridline is an **open-source, cross-platform database GUI client** for PostgreSQL (with MySQL, SQLite, and Redis to follow). It is built as a **Tauri 2.0 desktop app** — a lightweight native shell (~40MB baseline) around a React web frontend, with a Rust backend handling all database operations, CLI tool orchestration, and local persistence. **Core differentiators from commercial alternatives (DB Pro, TablePlus, Beekeeper Studio):** - **Everything free, nothing paywalled** — where DB Pro caps free users at 2 connections / 3 tabs / 5 saved queries, TablePlus caps at 2 open tabs + 2 windows, and Beekeeper reserves backup/restore, file import, multi-table export, ERD, and several DB connectors (Oracle, MongoDB, ClickHouse…) for paid tiers, Gridline ships the full feature set with no limits on tabs, connections, or saved queries - **DB-to-DB sync** — pipe-based `pg_dump` → `pg_restore` between two live connections; none of the alternatives (DB Pro, TablePlus, Beekeeper) offer direct DB-to-DB sync — they only back up to / restore from files - **Deeper PostgreSQL object explorer** — full detail views for Functions, Triggers, Sequences, Enums, and Extensions; Beekeeper and TablePlus show tables/views/routines/triggers but no sequences, enums, or extensions (Beekeeper can't even display routine definitions — issue #329 open since 2020), while DB Pro's tree stops at tables, views, indexes, and enums **Competitor reality check (verified 2026-05, from vendor docs/pricing/repos — keep this accurate):** - **DB Pro** (dbpro.app): **Electron app** (founder-confirmed on HN; launched Nov 2025) — not native despite "native macOS, Windows, Linux apps" marketing copy. Free plan = 2 connections / 5 saved queries / 3 open tabs / 2 dashboards / 2 table tags; data imports + SSH tunneling are paid-only per the pricing table/FAQ, while CSV/JSON export **does work on the free tier** (paid plans advertise "unlimited exports"; FAQ inconsistently claims "unlimited local connections"). Has query folders, dashboard folders, and table tags (roadmap 100%). No backup/restore (no pg_dump/pg_restore anywhere) and no DB-to-DB sync — the "Deeper Database Management" roadmap (indexes, users, constraints, VACUUM/ANALYZE) is still 0%. Schema tree: tables, views, indexes, relationships, and enums (since v1.6.0); MSSQL also lists stored procedures — no functions, triggers, sequences, or extensions on PG. Timeline: v1.0 Nov 2025 → v1.4 MSSQL/SSH/Keychain/Neon (Jan 2026) → v1.6 Redis/enums (Feb 2026) → self-hosted Studio (Mar 2026). Marketing overclaims ("native", Neon listed before it shipped) and known bugs (strict TLS verification blocks some Supabase pooler connections). - **Beekeeper Studio**: free Community edition = unlimited connections, no tab limits, saved queries, local folders (5.7+), staged Apply/Discard edits, basic query-result export. Paid-only: pg_dump/pg_restore backup/restore, file import, multi-table export, ERD, AI shell, JSON sidebar, cloud workspaces, and premium DB connectors (Oracle, MongoDB, ClickHouse, DuckDB…). Sidebar shows tables/views/matviews/routines/triggers — no sequences, enums, or extensions. - **TablePlus**: free = 2 open tabs / 2 windows / 2 advanced filters, but every other feature is included (incl. pg_dump/mysqldump backup GUI). No DB-to-DB sync, no ERD, no folder hierarchy. Sidebar: tables, views, functions, procedures. **Target audience:** Developers managing multiple database environments across projects (Personal, Work, Client). The workspace/folder hierarchy is a first-class concept. --- ## Tech Stack | Layer | Technology | Notes | | :--- | :--- | :--- | | Desktop shell | Tauri 2.0 | Native webview wrapper, Rust backend | | Frontend | React 19 + TypeScript 5.8 | Vite 7 for bundling/HMR | | Styling | Tailwind CSS | Dark-first, glassmorphic aesthetic | | State | Zustand or Jotai | Pick one and stay consistent per feature | | Editor | Monaco Editor | SQL mode with custom autocomplete providers | | Data grid | Glide Data Grid or TanStack Virtual | Virtualized, canvas-rendered | | Backend | Rust (tokio async runtime) | Connection pools, IPC commands, shell execution | | DB drivers | sqlx + tokio-postgres | Async, pure-Rust PostgreSQL driver | | Local storage | SQLite via rusqlite | User settings, workspace state, query history | | Credentials | OS keychain | macOS Keychain, Linux Secret Service, Windows Credential Manager | --- ## Project Structure ``` gridline/ ├── src/ # React frontend (TypeScript) │ ├── components/ # Reusable UI components │ │ ├── layout/ # App shell, sidebar, tabs │ │ ├── editor/ # Monaco wrapper, autocomplete │ │ ├── grid/ # Data grid, filters, export │ │ ├── tree/ # Workspace/object explorer tree │ │ └── ui/ # Primitives (buttons, modals, inputs) │ ├── stores/ # Zustand/Jotai stores │ ├── hooks/ # Custom hooks (useConnection, useQuery, etc.) │ ├── lib/ # Utilities, types, Tauri bindings │ │ ├── commands.ts # Typed wrappers around Tauri invoke() │ │ ├── types.ts # Shared TypeScript interfaces │ │ └── utils.ts # Formatting, validation helpers │ ├── App.tsx │ ├── main.tsx │ └── index.css # Tailwind directives + custom theme tokens ├── src-tauri/ # Rust backend │ ├── src/ │ │ ├── main.rs # Binary entry point │ │ ├── lib.rs # Tauri builder, command registration │ │ ├── db/ # Connection pooling, query execution │ │ │ ├── mod.rs │ │ │ ├── pool.rs # Connection pool manager │ │ │ └── introspection.rs # Schema/system catalog queries │ │ ├── commands/ # Tauri #[tauri::command] handlers │ │ │ ├── mod.rs │ │ │ ├── connections.rs # CRUD for saved connections │ │ │ ├── query.rs # SQL execution │ │ │ ├── schema.rs # Object tree introspection │ │ │ ├── schema_graph.rs # ER diagram / relationship graph │ │ │ ├── backup.rs # pg_dump / pg_restore wrappers │ │ │ └── workspace.rs # Workspace/folder persistence │ │ ├── models/ # Serde structs shared across commands │ │ │ ├── mod.rs │ │ │ ├── connection.rs │ │ │ ├── query.rs │ │ │ ├── db_viewer.rs # DB viewer types (SchemaGraph, TableNode, etc.) │ │ │ └── workspace.rs │ │ └── store/ # SQLite local persistence layer │ │ ├── mod.rs │ │ └── migrations.rs │ ├── Cargo.toml │ ├── tauri.conf.json │ └── capabilities/ # Tauri capability permissions ├── public/ # Static frontend assets ├── package.json ├── tsconfig.json ├── vite.config.ts ├── tailwind.config.ts └── AGENTS.md # This file ``` --- ## Conventions ### TypeScript / React - **Components:** PascalCase files, default exports for page-level, named exports for reusable primitives - **Hooks:** `use` prefix, one hook per file unless tightly coupled - **Stores:** One Zustand store per domain (`connectionStore`, `queryStore`, `workspaceStore`) - **Types:** Define interfaces in `src/lib/types.ts`; use `type` for unions/aliases - **No `any`:** Always type Tauri `invoke()` calls with explicit generics - **CSS:** Tailwind utility classes only; no CSS modules unless unavoidable (Monaco configuration is the exception) ### Rust - **Modules:** One module file per concern; re-export through `mod.rs` - **Errors:** Use `anyhow` for application errors, `thiserror` for library-style enums - **Commands:** Keep `#[tauri::command]` functions thin — delegate to `db/` or `store/` modules - **State:** Use Tauri managed state (`app.manage()`) for connection pool handles - **Naming:** `snake_case` for functions/modules, `CamelCase` for types/structs ### General - **IPC flow:** Frontend calls typed wrapper → wrapper calls `invoke()` → Rust command → Rust logic → returns `Result` - **Error handling:** Rust commands return `Result` (map errors to user-readable strings before crossing IPC boundary) - **No secrets in logs:** Never log connection strings, passwords, or query parameters - **Dark mode first:** All UI components must look correct in dark theme; light theme is secondary --- ## Key Design Decisions 1. **Tauri over Electron** — ~40MB RAM vs 250MB+. Native file dialogs, OS keychain access, and `std::process::Command` for `pg_dump`/`pg_restore` without Node.js overhead. 2. **Rust-native DB drivers** — `sqlx`/`tokio-postgres` connect directly to PostgreSQL from the Rust backend. No Node.js `pg` library, no sidecar Node process. The frontend never touches database connections directly. 3. **System CLI tools for backup/restore** — Rather than implementing `pg_dump` format parsers in Rust (enormous scope), we shell out to the user's installed `pg_dump`/`pg_restore` binaries. The app will detect missing tools and guide installation. 4. **SQLite for local state** — Workspace tree, saved queries, connection metadata (NOT passwords), and query history go into a local SQLite database in the Tauri app data directory. This enables fast full-text search and relational queries without loading everything into memory. 5. **Virtualized grid from day one** — Query results can be 100k+ rows. We must render with canvas/DOM virtualization (Glide Data Grid or TanStack Virtual), never with naive DOM row rendering. --- ## Development Workflow ### Commands ```bash bun install # Install frontend dependencies bun run dev # Vite dev server only (no Tauri) bun run tauri dev # Full Tauri app with hot-reload bun run tauri build # Production build cargo build # Rust backend only (from src-tauri/) cargo test # Rust tests ``` ### Releases Cut a release from the **`prod`** branch (never feature branches) by tagging it — `git tag vN.M.N && git push origin vN.M.N`. GitHub Actions (`release.yml`) builds installers for macOS (Apple Silicon + Intel), Windows, and Linux and opens a **draft** release (review + publish on GitHub). **Before tagging**, keep everything in sync: - Version number across `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` - `src/lib/version.test.ts` and `src/lib/docs-coverage.test.ts` if they assert the version - **Both README download tables** — the top **Download** section and the **Which file should I download?** section in Getting Started — they **hardcode** the current version in the asset filenames + direct `releases/download/...` links and must be bumped to the new version ### Adding a Tauri Command 1. Define the command function in the appropriate `src-tauri/src/commands/` module 2. Register it in `src-tauri/src/lib.rs` via `.invoke_handler(tauri::generate_handler![...])` 3. Create a typed wrapper function in `src/lib/commands.ts` 4. Call the wrapper from your React component/store ### Adding a New Dependency - **Frontend:** `bun add ` (runtime) or `bun add -d ` (dev) - **Rust:** Add to `src-tauri/Cargo.toml` under `[dependencies]` ### Testing Strategy - **Rust:** Unit tests for database logic, connection pool management, and command handlers. Use `sqlx::test` with a test PostgreSQL instance for integration tests. - **Frontend:** Vitest + React Testing Library for component tests. Focus on store logic, command wrappers, and critical UI flows (connection form, query execution). - **E2E:** (Future) Tauri WebDriver or Playwright for critical paths. --- ## Constraints & Guardrails - **Do NOT** implement `pg_dump` file format parsing — always shell out to system binaries - **Do NOT** store passwords in SQLite or local files — use OS keychain APIs exclusively - **Do NOT** render large query results in raw DOM — always use the virtualized grid component - **Do NOT** log credentials, connection strings, or query data - **Do NOT** introduce Electron, Node.js server processes, or Docker dependencies - **Do NOT** execute data-modifying SQL (INSERT, UPDATE, DELETE, DROP, ALTER) or any destructive CRUD operation (deleting connections, folders, tags) directly without explicit user confirmation. For database data, always push to the changes queue first and require "Commit All". For app entities (connections, folders, tags), show a confirmation dialog before executing. - **DO** keep Tauri commands thin — business logic lives in `db/` and `store/` modules - **DO** type all IPC boundaries explicitly - **DO** validate and sanitize all user-provided SQL and connection parameters before execution --- ## Implementation Status ✅ = Complete   🟡 = Partial/Stub   ❌ = Not Started Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of truth for what's next); this table reflects the current codebase and may lag planned work. See also the [architectural spec for the in-flight v0.7.0 work](./docs/superpowers/specs/2026-08-04-architectural-spec.md). ### Connection Management | Feature | Status | Details | | :--- | :---: | :--- | | Connections CRUD (PostgreSQL, MySQL, SQLite, Redis) | ✅ | Full create/read/update/delete with form validation | | New Connection screen (revamped) | ✅ | Two-stage entry → configured flow: Connection URI + 6-card provider grid (PostgreSQL / MySQL / SQLite / Redis / Supabase / NeonDB) with an OR divider → expands into label + tags/env/folder + General|SSH·SSL tabs. Supabase & NeonDB are managed-PostgreSQL presets (persist as `postgresql`) with in-app setup guides + SSL hints; SQLite swaps the URI field for a file-path + Browse input (v0.7.0) | | Connection testing (all DB types) | ✅ | PostgreSQL, MySQL, SQLite, Redis all testable | | DB Viewer: PostgreSQL browse + query | ✅ | Schemas, tables, paginated data, FK preview, JSON viewer | | DB Viewer: SQLite browse + query | ✅ | Full support via rusqlite | | DB Viewer: MySQL browse + query + edit | ✅ | Full viewer: connect (SSL + SSH tunnel), databases/tables/columns/FKs, query + pagination, inline cell editing + changes queue, DDL copy (`SHOW CREATE TABLE`), CSV/JSON import — added in v0.7.0. PK-only editing (no ctid equivalent); VARBINARY `information_schema` columns decoded correctly | | DB Viewer: Redis browse | ❌ | Connection + test only; browsing gated off with a clean "not supported" state (v0.7.0) | | Password storage in OS keychain | ✅ | macOS Keychain, Linux Secret Service, Windows Credential Manager | | SSH tunnel config UI | ✅ | Host, port, user, auth method, key path, passphrase fields | | SSH tunnel runtime | ✅ | Real ssh2 tunnel (password + key auth), binds 127.0.0.1 only, secrets in OS keychain (`ssh_password:` / `ssh_passphrase:`), closed on pool eviction / app exit; TLS downgraded to `require` through the tunnel | | SSL/TLS config UI | ✅ | Mode (disable/require/verify-ca/verify-full), cert paths | | SSL/TLS runtime | ✅ | PostgreSQL all modes via rustls (disable/require/verify-ca/verify-full; **v1: `verify-ca` behaves as `verify-full`** — documented refinement), client certs PKCS#1/PKCS#8/EC, encrypted client keys rejected; MySQL test path maps modes (verify-full → VerifyIdentity) | ### Home Screen & Organization | Feature | Status | Details | | :--- | :---: | :--- | | Connection cards grid (by folder) | ✅ | Grouped display, single-click to open DB viewer | | Folders CRUD | ✅ | Nested folders, reparent on delete, breadcrumb nav | | Tags CRUD | ✅ | Colors, drag reorder, filter connections by tag | | Tag overflow scroll on cards | ✅ | Connection cards show up to 3 tags, then the row scrolls horizontally (v0.7.0) | | Tag filter dropdown | ✅ | ActionRow Tags button → dropdown with checkboxes, active-count badge, Manage tags → Settings. **OR semantics** — a connection shows if it has ANY selected tag (not all) | | Folder tag matching | ✅ | When any filter is active, folder cards show only if the folder matches a selected tag OR contains matching connections (directly or in subfolders) | | DB type filter (Postgres/MySQL/SQLite/Redis) | ✅ | Dropdown with checkboxes + Clear all; folder cards hidden when their contents don't match the DB type | | Environment filter | ✅ | Select in Filters dropdown: All / Production / Staging / Development / None (unassigned); counts toward active badge | | Global search (Cmd+K) | ✅ | Connection URL detection auto-fills new-connection form; shows results from ALL folders as if at root (folder scope bypassed while searching); breadcrumb shows "Showing Search Results" with Clear button; **Esc while the search is focused clears the query, exits search mode and blurs** | | Connection name editing | ✅ | Name field in GeneralTab edit form | | Import/Export connections (JSON) | ✅ | Bulk import with validation, skipped-record reporting | | Bulk select + delete connections/folders | ✅ | Checkbox selection with confirmation dialog | | Drag-and-drop connections to folders | ✅ | Optimistic update with atomic snapshot rollback (race-condition hardened) | | Inline tag creation | ✅ | "Create first tag" inline form (name + color) in SearchableTagPicker empty state | | Move-to-folder bulk action | ✅ | Selection toolbar → Move to Folder dialog (folder picker, move confirmed via dialog) | | Favorites / Recent connections | ✅ | Star toggle in the connection card ⋮ menu (persisted `favorite` flag); Recent connections row (top 8 via `getRecentConnections`) | | Connection status indicator on cards | ✅ | Kebab menu → Test connection with inline idle/checking/online/offline result, on-demand via keychain + `testConnection`. **Reports real `server_version` + `latency_ms`** (PG/MySQL/SQLite queries + connect timing in the Rust backend); shows `Online · 16.4 · 42ms` or the error, re-check debounced 2s | | Connection card actions menu (⋮) | ✅ | Kebab dropdown: Favorite toggle, Test connection (inline status), Manage submenu (Edit… / Duplicate / Delete…) | ### Database Viewer | Feature | Status | Details | | :--- | :---: | :--- | | Multi-tab table browser | ✅ | Open tables in tabs, close with Cmd/Ctrl+W | | DB viewer capability gating | ✅ | `dbCapabilities.ts` matrix per `db_type` (PG full; SQLite explorer/queries/visualizer/editing/import; MySQL explorer/queries/editing/import; Redis none); unsupported views show a clean "not supported" state. Redis browsing gated off (v0.7.0) | | Schema/database selector | ✅ | Ghost-style dropdowns, single-row layout; schema dropdown + tables tree show a loading state while the schema tree is still fetching, instead of an empty "no tables" state (v0.7.0) | | Table toolbar during load | ✅ | Toolbar renders immediately when a tab opens while data is still fetching, so the loading state is visible (v0.7.0) | | Refresh database (spin + success/error feedback) | ✅ | Re-fetches databases, schemas, and tables | | Search tables filter | ✅ | Animated input, real-time filter by name, auto-hide on blur | | Column metadata (PK, FK, type, nullable, default) | ✅ | Expand table row to see columns with icons. ENUM/custom types resolved via udt_name, cast ::text for data retrieval. | | FK detection | ✅ | `information_schema.constraint_column_usage` + `PRAGMA foreign_key_list` | | FK preview popover | ✅ | Click FK cell → popover with referenced row → "Open" button creates filtered tab | | JSON/JSONB cell popover | ✅ | Formatted/Raw tabs with copy button | | Smart default sort | ✅ | 12-tier priority: updated_at → created_at → *_at → *_id → seq/rank/version | | Data grid pagination | ✅ | Page nav, page size selector persisted in settings | | Column filtering (server-side) | ✅ | eq, neq, contains, starts, ends, gt, lt, null, notnull pushed to SQL WHERE | | Visual filter builder | ✅ | Drag-and-drop column palette (@dnd-kit) with type-aware operators (textish → contains, else eq), AND semantics, persists in tab `filterRules` | | Column sorting (server-side) | ✅ | Multi-column asc/desc pushed to SQL ORDER BY | | Column show/hide | ✅ | Toggle visibility per column | | Column resize (drag handle) | ✅ | Double-click to auto-fit | | Row selection (checkboxes + select all) | ✅ | Bulk copy (JSON/CSV/SQL) and delete | | Export toolbar (JSON, CSV, SQL, Markdown) | ✅ | Client-side Blob download of visible rows | | Auto-refresh timer | ✅ | Configurable interval in settings | | Changes queue (INSERT, UPDATE, DELETE, bulk_insert, empty_table, drop_table) | ✅ | Stage → **Commit All**. Tab bar **Changes** button (amber border + count badge when pending) toggles a **popover** anchored to it: header with **Visual/SQL** toggle (cards showing op badge + table + description + per-change **Revert**, or a generated-SQL preview via `buildChangeSql`), footer **Clear All** + **Commit All (N)** with **⌘S/Ctrl+S** shortcut. Committed cards show a green ✓ (failed ✗); committing `drop_table` auto-closes open tabs of that table | | Auto schema-tree refresh | ✅ | Tree auto-refreshes after a successful schema-modifying query run (`CREATE`/`DROP`/`ALTER`/`TRUNCATE` via `isSchemaModifyingQuery`) and after committing `drop_table` via the queue — no manual refresh needed | | Data import (CSV/JSON) | ✅ | Table overflow menu → ImportDialog: file pick, parse, preview (first 100 rows), header→column mapping, caps 100k rows / 100 MB; stages a bulk_insert change through the queue → Commit All | | Table menu actions | ✅ | Copy table schema (DDL via pg_dump / sqlite_master), Empty Table (DELETE) / Delete Table (DROP) through the queue with confirm, export stubs wired (JSON/CSV/SQL/Markdown) | | Edit connection modal (from DB viewer) | ✅ | AnimatedModal with keychain password fetch on test | | Connection drop banner | ✅ | Auto-detects broken connections with reconnect prompt | | Inline cell editing | ✅ | Double-click/Enter edits a cell; commit stages an `update` change in the queue → Commit All. No-PK tables use ctid/rowid locator; PK/generated/identity columns and views/matviews are read-only. Stale-write protection via affected-row-count check. **Optimistic UI**: the changes queue is the single source of truth — `deriveStagedValues` feeds staged values + a pending amber dot back into the grid (dot clears on commit, values survive until refetch); re-editing the same cell replaces the queue entry (original `oldData` kept); Clear All removes dots instantly; queue cards show an old → new value diff. **Smart editors**: PG enum columns render a `