This file is an immunity system. Every rule below exists because an agent once made this mistake. When you find a new failure mode, add a rule here so it never happens again.
- After modifying any
.rsfile, always runcargo checkbefore declaring done. TypeScript compilation alone does not catch Rust errors. - In a dirty worktree, do not run broad
cargo fmtfor a narrow Rust change. Format only the Rust files touched by the task so unrelated user WIP is not rewritten. - Structured errors must cross the backend from the inside out. New or modified
service/internal code should return
Result<T, AppError>fromsrc-tauri/src/error.rs; Tauri commands returnResult<T, AppError>directly —AppErrorimplementsSerializeand is sent as a structured JSON object{code, message, hint, category}to the frontend. Do not use.map_err(String::from)at the command boundary. Database drivers are still migrating, so do not introduce new string-tag protocols like[VALIDATION_ERROR],[UNSUPPORTED], or[ERR-1001]; when touching driver code, migrate towardDbErrororAppErrorinstead of parsing strings. - When adding a new database driver, do three things — not two, not four:
- Add the module in
src-tauri/src/db/drivers/<name>.rs - Add the
pub mod <name>;declaration insrc-tauri/src/db/drivers/mod.rs - Register it in the
connect()match arms and imports insrc-tauri/src/db/drivers/registry.rs
- Add the module in
- When a driver's type is MySQL-family (mariadb, tidb, starrocks, doris),
update
is_mysql_family_driver()insrc-tauri/src/db/drivers/registry.rsif the new driver belongs there. - Oracle tests require the Oracle Instant Client installed locally. The
scripts/test-integration.shscript detects this viaDYLD_LIBRARY_PATHand common paths. Integration tests for Oracle will be skipped if the client is missing — do not try to "fix" the test to run without it. - Error messages for connection failures must use
conn_failed_error()insrc-tauri/src/db/drivers/mod.rs. This provides context-aware hints (TLS, auth, network). Raw error strings confuse users. - Integration tests are marked
#[ignore]and requireIT_DB=<name>env variable to run. They also need Docker. Do not remove#[ignore]to "make tests pass" in CI.
src/services/api/core.tsis the only file that callsinvoke(). Never call@tauri-apps/api/coreinvoke directly from components or other services. Domain wrappers live insrc/services/api/{query,metadata,redis,...}.ts.invoke()insrc/services/api/core.tshas a single typed overload constrained byCommandMap. Never writeinvoke<ReturnType>(...)— the return type is inferred from the command name. Explicit generics bypass type checking by falling through to an unconstrained overload.- When a Tauri command's parameter types change, update both:
- The Rust
#[tauri::command]signature - The corresponding TypeScript wrapper in
src/services/api/
- The Rust
- Mock mode (
VITE_USE_MOCK=true) is for rapid UI iteration. The mock implementations live insrc/services/mocks/. When adding a new API method toapi/, always add a corresponding mock entry. - i18n locale files are TypeScript, not JSON. After adding a new locale file
in
src/lib/i18n/locales/, register it insrc/lib/i18n/index.ts. - Until the i18n entry point composes the split domain locale modules, update
translation keys in both
locales/{en,zh}.tsand the matchinglocales/{en,zh}/<domain>.tsfiles. The app currently imports the monolithic files, so changing only a split domain file has no runtime effect. - The keyboard-shortcut registry lives in
src/lib/shortcuts/defaults.ts. TheShortcutsProviderinsrc/contexts/ShortcutsContext.tsxis the ONLY component allowed to callgetSetting/saveSettingfor theshortcuts.v1store key (mirrors the api.ts-invokes-Tauri rule above). Components must read bindings viauseShortcutBinding(id),useShortcutBindings(), oruseShortcutMatcher(), never directly from the store. When adding a new shortcut, add an entry toSHORTCUT_DEFAULTS, a label undersettings.shortcuts.label.*in every locale, and wire the matcher call in the appropriate handler site — do not hard-code the key. - Icon-only buttons that trigger user actions must have an accessible name
(
aria-label, and usually matchingtitle) from i18n text. Without this, Playwright flow tests have to click by position and can miss the exact "button exists but crashes when clicked" regression they are meant to catch. - Effects that set React state must not depend on unstable callback identities
unless the callback is deliberately memoized. For "read latest callback but
do not re-run" cases, store the callback in a ref. When updating
Set/Mapstate inside effects, return the previous object if the key/value is already present so a no-op does not trigger a render loop.
- Every driver implements the
DatabaseDrivertrait re-exported bysrc-tauri/src/db/drivers/mod.rs. The trait has required methods (connect,list_databases,list_tables,get_table_structure,get_table_metadata,get_table_ddl,get_table_data,get_table_data_chunk,execute_query,get_schema_overview,close) and optional ones (list_routines,get_routine_ddl,execute_query_with_id). - Do not add driver-specific connection logic outside the driver module. SSH tunneling is handled transparently in the connection layer, not in individual drivers.
- SQL statement splitting lives in
src-tauri/src/db/sql/splitter.rs(split_sql_statements,first_sql_keyword).src-tauri/src/db/drivers/mod.rsonly re-exports splitter helpers as a compatibility layer. Do not reimplement SQL parsing in individual drivers. TableInfo.typeis a logical object category consumed by sidebar grouping, not a database-specific storage engine. Drivers must return the categories expected by the tree (table/BASE TABLEand the driver's view categories); expose engine details through table metadata instead.
- Rust integration tests follow three levels:
<db>_integration.rs— direct driver method testing<db>_command_integration.rs— ephemeral connection commands<db>_stateful_command_integration.rs— saved connection workflows All three must be added toscripts/test-integration.shwhen adding a new database.
- Integration tests run with
--test-threads=1. Do not change this — database containers are shared and parallel tests collide. - Use
IT_REUSE_LOCAL_DB=1to skip container creation during local development iterations.
- The GitHub release workflow's macOS updater path only requires the signed
.app.tar.gzupdater bundle. Do not let optional DMG generation block CI unless the release intentionally needs a DMG asset; usetauri build --bundles appfor macOS updater builds.
CLAUDE.mdis a table of contents — it points to deeper docs but does not duplicate them. If you need architecture details, readdocs/architecture.md. If you need commands, readdocs/commands.md.- Do not add instructions to
CLAUDE.mdthat apply only in a specific context. Add them here inAGENTS.mdinstead, or in a skill file under.claude/skills/. - When you encounter a new failure mode during this session, add an entry to this file. The file grows as the project's institutional knowledge grows.
The following files are modification hotspots (most commits in last 30 days). Before modifying them, check line count. If over threshold, suggest extracting logic before adding more.
| File | Threshold | Extraction strategy |
|---|---|---|
src/components/business/DataGrid/TableView.tsx |
500 lines | Extract to hooks in tableView/hooks/ |
src/components/business/Sidebar/ConnectionList.tsx |
500 lines | Split tree state, dialog state, action handlers |
src-tauri/src/commands/query.rs |
600 lines | Extract helpers to db/ or services/ |
src-tauri/src/commands/metadata.rs |
600 lines | Extract helpers to db/ or services/ |
src-tauri/src/lib.rs |
500 lines | Move command registration to module-level helpers |
src/lib/i18n/locales/*.ts |
800 lines | Split by domain, re-export from index |
Rules:
- When modifying a hot file, first check
wc -l. If over threshold, inform the user and suggest extraction targets before proceeding. - Do not add new top-level functions or handlers to hot files. Extract to hooks, helpers, or service modules first, then import.
- For
TableView.tsx: only orchestration and layout belong here. State logic, event handlers, and data transformations go intableView/hooks/. - For
ConnectionList.tsx: split intouseTreeState,useDialogState,useConnectionActionshooks. - For
lib.rs: prefercommands::<module>::register(builder)pattern over listing every command inline.