9.5 KiB
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, etc.):
- No paywalls — unlimited tabs, connections, and saved queries by default
- First-class PostgreSQL administration:
pg_dump,pg_restore, DB-to-DB sync - Full object explorer: Functions, Triggers, Sequences, Enums, Extensions — not just tables
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
│ │ │ ├── backup.rs # pg_dump / pg_restore wrappers
│ │ │ └── workspace.rs # Workspace/folder persistence
│ │ ├── models/ # Serde structs shared across commands
│ │ │ ├── mod.rs
│ │ │ ├── connection.rs
│ │ │ ├── query.rs
│ │ │ └── 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:
useprefix, one hook per file unless tightly coupled - Stores: One Zustand store per domain (
connectionStore,queryStore,workspaceStore) - Types: Define interfaces in
src/lib/types.ts; usetypefor unions/aliases - No
any: Always type Tauriinvoke()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
anyhowfor application errors,thiserrorfor library-style enums - Commands: Keep
#[tauri::command]functions thin — delegate todb/orstore/modules - State: Use Tauri managed state (
app.manage()) for connection pool handles - Naming:
snake_casefor functions/modules,CamelCasefor types/structs
General
- IPC flow: Frontend calls typed wrapper → wrapper calls
invoke()→ Rust command → Rust logic → returnsResult<T, String> - Error handling: Rust commands return
Result<T, String>(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
-
Tauri over Electron — ~40MB RAM vs 250MB+. Native file dialogs, OS keychain access, and
std::process::Commandforpg_dump/pg_restorewithout Node.js overhead. -
Rust-native DB drivers —
sqlx/tokio-postgresconnect directly to PostgreSQL from the Rust backend. No Node.jspglibrary, no sidecar Node process. The frontend never touches database connections directly. -
System CLI tools for backup/restore — Rather than implementing
pg_dumpformat parsers in Rust (enormous scope), we shell out to the user's installedpg_dump/pg_restorebinaries. The app will detect missing tools and guide installation. -
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.
-
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
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
Adding a Tauri Command
- Define the command function in the appropriate
src-tauri/src/commands/module - Register it in
src-tauri/src/lib.rsvia.invoke_handler(tauri::generate_handler![...]) - Create a typed wrapper function in
src/lib/commands.ts - Call the wrapper from your React component/store
Adding a New Dependency
- Frontend:
bun add <package>(runtime) orbun add -d <package>(dev) - Rust: Add to
src-tauri/Cargo.tomlunder[dependencies]
Testing Strategy
- Rust: Unit tests for database logic, connection pool management, and command handlers. Use
sqlx::testwith 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_dumpfile 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 keep Tauri commands thin — business logic lives in
db/andstore/modules - DO type all IPC boundaries explicitly
- DO validate and sanitize all user-provided SQL and connection parameters before execution
Related Documents
This file is read by AI coding agents (Claude, Cursor, Copilot, etc.) to understand project conventions and architecture before making changes. Keep it current as the project evolves.