dbflux
AGENTS.md526 starsChanged 6 months agoApache-2.0View on GitHub
Guidelines for AI agents working in this Rust/GPUI codebase. DBFlux is a keyboard-first database client built with Rust and GPUI (Zed's UI framework). For project structure, crate boundaries, key files, and subsystem overviews, use ARCHITECTURE.md as the canonical reference. For the branching model, version rules, tag flow, and release procedure, use docs/RELEASE.md as the canonical reference. For contributor workflow and the label taxonomy, use CONTRIBUTING.md. The automated release skill (skills/dbflux-release/SKILL.md) follows the same rules. Both are created from the repository's…
- Deletes or force-pushes
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository8 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- branch-prSkill
- dbflux-builtin-auth-providerSkill
- dbflux-builtin-driverSkill
- dbflux-releaseSkill
- dbflux-rpc-auth-providerSkill
- dbflux-rpc-driverSkill
- issue-creationSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
43 rules. About 0 are common, 11 are distinctive, and 32 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
32
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 21
Use only labels from the taxonomy in CONTRIBUTING.md § Label Guide, and never invent labels such as area:*.
- Very specific: line 95
Launching DBFlux for a session: Always isolate the data directories, so the session never reads or writes the user's real configuration, …
- Very specific: line 114
Wait for the result with wait_for_element or wait_for_state instead of sleeping.
- Very specific: line 116
Check the outcome in the element tree, and with screenshot when the change is visual.
- Very specific: line 130
Stop the DBFlux process when the session ends, and never commit screenshots or recordings.
- Very specific: line 132
Never add ui-automation to a default feature list.
- Distinctive: line 139
Do not write comments that summarize code.
- Very specific: line 141
Keep tests in the module's tests submodule (foo/tests.rs or foo/tests/), not inline in large files and not as sibling *_tests.rs files.
- Distinctive: line 142
Keep functions under ~100 lines.
- Distinctive: line 143
Avoid creative additions unless explicitly requested
- Distinctive: line 144
Use full words for variable names (no abbreviations like "q" for "queue")
- Very specific: line 148
Avoid unwrap() and functions that panic.
- Very specific: line 155
If you genuinely want to drop a value — not an error — bind it to _name instead of _.
- Distinctive: line 156
Ensure async errors propagate to the UI so users get meaningful feedback.
- Very specific: line 170
Use mod.rs for module directories (e.g., views/mod.rs, not a sibling views.rs)
- Very specific: line 171
When creating crates, specify library root in Cargo.toml with [lib] path = "..."
- Distinctive: line 175
Use variable shadowing to scope clones in async contexts:
- Very specific: line 224
Use the inner cx inside closures, not the outer one, to avoid multiple borrows.
- Distinctive: line 228
All entity/UI work happens on the foreground thread.
- Very specific: line 265
Use .when(condition, |this| ...) for conditional attributes/children
- Very specific: line 266
Use .when_some(option, |this, value| ...) for Option-based conditionals
- Very specific: line 267
Call cx.notify() when state changes affect rendering
- Very specific: line 272
Every window must render under gpui_component::Root, which sets the rem size from the UI font size.
- Very specific: line 275
Do not write text_size(px(..)).
- Very specific: line 279
Use pending_* fields with .take() to safely update other entities or open modals:
- Very specific: line 306
Keyboard coverage: Every clickable element needs a keyboard path (a bound Command or a menu entry) registered in its surface's coverage …
- Very specific: line 308
Focus tracking: Use .track_focus(&focus_handle) on container elements to receive key events:
- Distinctive: line 317
Mouse/keyboard sync: When a component supports both mouse and keyboard navigation, sync state on mouse events:
- Very specific: line 327
Input blur race condition: When switching between inputs via click, the old input's Blur event fires after the new input's mousedown.
- Very specific: line 388
No CloudWatch/MongoDB/Redis/etc.
- Very specific: line 403
Use abstractions from DriverMetadata:
- Very specific: line 479
Add feature flag in crates/dbflux/Cargo.toml (binary) and crates/dbflux_app/Cargo.toml.
- Very specific: line 504
Keep headings, relative links, and ``mermaid fences aligned with the English page, because the site pairs pages by structure.
- Very specific: line 508
Every driver crate under crates/dbflux_driver_*/ must include a README.md.
- Distinctive: line 509
Keep each driver README focused on two sections: Features and Limitations.
- Distinctive: line 510
Update driver README files whenever capabilities, supported operations, or known limits change.
- Very specific: line 525
Create crates/dbflux_ui_document/src/<name>/mod.rs with the entity
- Very specific: line 526
Create crates/dbflux_ui_document/src/<name>/pane.rs with into_pane(entity, cx) -> PaneHandle
- Very specific: line 527
Add a DocumentKey variant in crates/dbflux_ui_document/src/dedup.rs if dedup is needed
- Very specific: line 528
Add open_<name> in crates/dbflux_ui/src/ui/views/workspace/actions.rs
- Very specific: line 699
Read channel identity through the accessors — app_id(), display_name(), db_file_name() — never branch on the raw version string and never …
- Very specific: line 721
Write markdown that reads correctly on GitHub.
- Distinctive: line 739
Never send a reader to the repository for something the site renders.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md — DBFlux
Guidelines for AI agents working in this Rust/GPUI codebase.
Project Overview
DBFlux is a keyboard-first database client built with Rust and GPUI (Zed's UI framework).
For project structure, crate boundaries, key files, and subsystem overviews, use ARCHITECTURE.md as the canonical reference.
For the branching model, version rules, tag flow, and release procedure, use docs/RELEASE.md as the canonical reference. For contributor workflow and the label taxonomy, use CONTRIBUTING.md. The automated release skill (skills/dbflux-release/SKILL.md) follows the same rules.
Issues and Pull Requests
Both are created from the repository's own templates — never from a remembered or invented shape:
- An issue uses one of the forms in
.github/ISSUE_TEMPLATE/—bug_report.ymlfor a defect,feature_request.ymlfor a capability. Fill the form's fields in the order it declares them, and keep the[bug]/[feature]title prefix and theLabels I plan to applychecklist it renders. - A pull request uses
.github/pull_request_template.mdverbatim:Summary,What does this resolve?,How was this solved?,Validation,Where was this tested?,Checklist,Labels to apply. Do not add sections of your own; follow-up work and known limitations belong where the template asks for them. - The mechanics (duplicate search, choosing the form,
ghcommands, reading the full branch delta) live inskills/issue-creation/SKILL.mdandskills/branch-pr/SKILL.md. - Open the issue first and wait for maintainer approval (the
status:approvedlabel) before implementing a feature or behavior change; a bug fix may open its issue and PR together, and the maintainer may skip the wait. PRs are squash-merged, so split large work into independent PRs frommainrather than long stacks (seeCONTRIBUTING.md§ Pull Requests). - Use only labels from the taxonomy in
CONTRIBUTING.md§ Label Guide, and never invent labels such asarea:*. - A contributor without write access to
0xErwin1/dbfluxcannot apply labels from a fork. State the labels the change needs where the template asks for them and ask a maintainer to apply them during triage — do not drop that part of the template.
Build & Run Commands
cargo check --workspace # Fast type checking
cargo build -p dbflux --features sqlite,postgres,mysql,mssql,mongodb,redis,dynamodb,cloudwatch,influxdb,redshift,s3,aws # Debug build
cargo build -p dbflux --features sqlite,postgres,mysql,mssql,mongodb,redis,dynamodb,cloudwatch,influxdb,redshift,s3,aws --release # Release build
cargo run -p dbflux --features sqlite,postgres,mysql,mssql,mongodb,redis,dynamodb,cloudwatch,influxdb,redshift,s3,aws # Run app
# MCP server (AI integration) - included by default
cargo build -p dbflux # MCP included in default features
./target/debug/dbflux mcp --client-id test-client
# Build without MCP support (smaller binary, no AI integration)
cargo build -p dbflux --no-default-features --features sqlite,postgres,mysql,mssql,mongodb,redis,dynamodb,cloudwatch,influxdb,redshift,s3,lua,aws
python3 scripts/lint.py fmt # Format
python3 scripts/lint.py clippy # Lint
cargo test --workspace # All tests
cargo test --workspace test_name # Single test
cargo test -p dbflux_core # Tests in specific crate
cargo test -p dbflux_driver_dynamodb --test live_integration -- --ignored # Docker-backed live tests
# Preferred test runner: always use `cargo nextest run` over `cargo test` when
# available (provided by the Nix dev shell). It is faster and gives clearer
# output. Note it does NOT run doctests, so run those separately.
cargo nextest run --workspace # All tests (unit + integration)
cargo test --doc --workspace # Doctests (run separately)
cargo nextest run -p dbflux_driver_sqlite --run-ignored all # Include #[ignore]d live tests
# Nix
nix develop # Enter dev shell
nix build # Build package
nix run # Run directlyscripts/lint.py covers only the first-party crates under crates/ and never
lints or reformats the vendored crates under vendor/.
Linux build requirement: .cargo/config.toml links the
x86_64-unknown-linux-gnu target with -fuse-ld=mold, so the mold linker
must be on PATH for any local cargo build/test/check. The Nix dev shell
and CI provide it; non-Nix Linux setups must install mold via their package
manager. Windows and macOS use their default linker and are unaffected.
The renderer's font stack needs no fontconfig development headers:
.cargo/config.toml sets RUST_FONTCONFIG_DLOPEN, so libfontconfig is loaded
at runtime instead of linked, and a machine without it loses font discovery
rather than failing to start the binary. freetype follows its own build script,
which uses the system library when a freetype2.pc of at least the required
version is present and otherwise compiles its vendored sources, so a missing
development package costs build time rather than breaking the build.
Verifying UI Changes with UI Automation
A build with the ui-automation feature exposes every DBFlux window to a local
MCP server, so an agent can check a UI change in the running app: read the
element tree, click, type and take screenshots. Use it after a change to
rendering, focus, keyboard handling or a user flow, in addition to the GPUI
tests, not instead of them. Setup, the full tool list and the trust model are in
docs/UI_AUTOMATION.md.
Setup (once per machine). Build the server and register it with your MCP
client. Claude Code only loads a newly registered server in a new session, so if
the dbflux-ui tools are not available, ask the user to register it and restart
rather than working around it:
cargo build -p gpui-mcp-server
claude mcp add dbflux-ui -- "$PWD/target/debug/gpui-mcp" --app-id dbfluxLaunching DBFlux for a session. Always isolate the data directories, so the
session never reads or writes the user's real configuration, connections or
dbflux.db. On a Wayland session, clear WAYLAND_DISPLAY so DBFlux runs under
XWayland, which screenshots need. Run it in the background and record its PID:
scratch="$(mktemp -d)"
WAYLAND_DISPLAY= GPUI_X11_SCALE_FACTOR=1 \
XDG_DATA_HOME="$scratch/data" XDG_CONFIG_HOME="$scratch/config" \
XDG_STATE_HOME="$scratch/state" XDG_CACHE_HOME="$scratch/cache" \
cargo run -p dbflux --features ui-automationWorking loop.
- Find the target with
get_ui_treeorfind_elements. Elements are addressed by their GPUI element id (.id("rail-scripts")becomesrail-scripts). - Act with
click_element,type_text,set_textorkeyboard(GPUI keystroke syntax, for examplectrl-shift-p). - Wait for the result with
wait_for_elementorwait_for_stateinstead of sleeping. - Check the outcome in the element tree, and with
screenshotwhen the change is visual. Look at the image before reporting that the change works.
Rules.
- Give new interactive elements (buttons, tabs, inputs, list rows) a stable,
descriptive
.id(...), so they can be targeted by name rather than by coordinates. - Mark new secret inputs with
controls::Input::secret(true), or derive the flag fromform_renderer::is_secret_field, so their value stays out of the element tree. - Connect only to local test databases (SQLite files in the scratch directory, or the Docker services used by the live integration tests). A query run through the UI bypasses MCP policies and approvals.
- Stop the DBFlux process when the session ends, and never commit screenshots or recordings.
- Never add
ui-automationto adefaultfeature list.
Rust Guidelines
General Principles
- Prioritize correctness and clarity over speed
- Do not write comments that summarize code; only explain non-obvious "why"
- Keep code organized by module: extend the module that owns the behavior, and once a file passes ~1000 lines of production code, split it into a module directory (
foo/mod.rsplus submodules) by responsibility. New files belong inside the module directory they serve, not loose next to unrelated files. - Keep tests in the module's
testssubmodule (foo/tests.rsorfoo/tests/), not inline in large files and not as sibling*_tests.rsfiles. - Keep functions under ~100 lines; split new or substantially rewritten functions that exceed it into private helpers. Existing functions and files over these limits are exempt until they are refactored.
- Avoid creative additions unless explicitly requested
- Use full words for variable names (no abbreviations like "q" for "queue")
Error Handling
- Avoid
unwrap()and functions that panic; use?to propagate errors - Be careful with indexing operations that may panic on out-of-bounds
- Never use
let _ =on a fallible expression. Silently discardingResult/Optionerrors hides real failures from users, logs, and the audit trail. This rule has no exceptions for "fire and forget" — pick one of the alternatives below instead:- Propagate with
?when the caller should handle it. - Log with
.log_err()(fromdbflux_core) when ignoring but wanting at least one stderr/audit trace. - Branch explicitly with
match/if let Err(e) = ...when you need custom logic. - Surface to the user with
report_error/report_error_async(dbflux_ui_base::user_error) when the failure is something the user just triggered. Producing a toast + audit row is preferred overlog::error!for any user-facing operation; this also drives the status-bar error badge. - If you genuinely want to drop a value — not an error — bind it to
_nameinstead of_. Reserve the barelet _ =pattern for that intent only.
- Propagate with
- Ensure async errors propagate to the UI so users get meaningful feedback. Background tasks that fail without a path to the foreground are a bug, not a feature.
User-facing error reporting (dbflux_ui_base::user_error)
When a user-triggered operation fails (storage, network, driver, config, hook, auth), route it through the centralized seam instead of log::error! + manual Toast::error(...). The seam attaches a UUID v7 correlation id to the toast and the audit row, drives the status-bar error badge, and provides a "View in Audit" action wired to that correlation id.
- Foreground (
&mut App/&mut Context<T>):report_error(UserFacingError::new(ErrorKind::Storage, msg), cx) - Background (
cx.spawn(async ...)/background_executor):report_error_async(UserFacingError::new(ErrorKind::Network, msg), &cx) - Driver errors: prefer
UserFacingError::from_formatted(ErrorKind::Driver, fe)so the driver'sErrorFormatteroutput feedscausedirectly — keeps the UI driver-agnostic. ErrorKindvariants:Storage,Network,Auth,Hook,Driver,User,Config.- Convention: only the first catch site reports. Propagators above must NOT re-report — there is no runtime deduplication, double-toasts are a code-review concern.
File Organization
- Use
mod.rsfor module directories (e.g.,views/mod.rs, not a siblingviews.rs) - When creating crates, specify library root in
Cargo.tomlwith[lib] path = "..."
Async Patterns
Use variable shadowing to scope clones in async contexts:
executor.spawn({
let task_ran = task_ran.clone();
async move {
*task_ran.borrow_mut() = true;
}
});Performance Patterns
Pre-compute expensive operations: Move string formatting and allocation into constructors rather than during rendering:
// Good: Format once during construction
CellValue::Text { display: format!("{}", value), ... }
// Bad: Format on every render
fn render(&self) { format!("{}", self.value) }Lazy loading for large datasets: Drivers should return shallow metadata initially and fetch details on-demand:
fn get_tables(&self) -> Vec<TableInfo> // Names only
fn table_details(&self, name: &str) -> TableDetails // Columns, indexesDriver error formatting: Drivers implement the ErrorFormatter trait from dbflux_core/src/core/error_formatter.rs to extract detailed error info. PostgreSQL's as_db_error() provides detail, hint, column, table, and constraint fields. MongoDB extracts error codes and labels. Use structured error formatting instead of raw format!("{:?}", e).
GPUI Guidelines
Context Types
App— root context for global state and entity accessContext<T>— provided when updatingEntity<T>, derefs toAppAsyncApp/AsyncWindowContext— fromcx.spawn, can cross await pointsWindow— window state, passed beforecxwhen present
Entity Operations
With thing: Entity<T>:
thing.read(cx)→&Tthing.update(cx, |thing, cx| ...)→ mutate withContext<T>thing.update_in(cx, |thing, window, cx| ...)→ also providesWindow
Use the inner cx inside closures, not the outer one, to avoid multiple borrows.
Concurrency
All entity/UI work happens on the foreground thread.
// Background work + foreground update
let task = cx.background_executor().spawn(async move {
// expensive work
});
cx.spawn(async move |_this, cx| {
let result = task.await;
cx.update(|cx| {
entity.update(cx, |state, cx| {
state.pending_result = Some(result);
cx.notify();
});
}).ok();
}).detach();Task handling:
- Await in another async context
task.detach()ortask.detach_and_log_err(cx)for fire-and-forget- Store in a field if work should cancel when struct drops
Rendering
Types implement Render for element trees with flexbox layout:
impl Render for MyComponent {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().border_1().child("Hello")
}
}- Use
.when(condition, |this| ...)for conditional attributes/children - Use
.when_some(option, |this, value| ...)for Option-based conditionals - Call
cx.notify()when state changes affect rendering
Font Size Scaling
- Interface sizes that follow the UI font size are rems built with
dbflux_components::tokens::ui(px): font sizes, text row and control heights, inline icons, and widths that hold text. Spacing, borders, radii and layout panel sizes stayPixels. - Every window must render under
gpui_component::Root, which sets the rem size from the UI font size. A window without it keeps the default 16 px rem and ignores the setting. - Pixel arithmetic on a rem token converts first:
token.to_pixels(window.rem_size()), orfonts::ui_px(cx, token)without aWindow. - The data grid and the code editor follow their own font sizes: use
fonts::grid_*/fonts::grid_pxandfonts::editor_*/fonts::editor_scaled, nottokens::ui. - Do not write
text_size(px(..)). Thedbflux_componentsstyle guardrails reject it in every UI crate.
Entity Updates in Render
Use pending_* fields with .take() to safely update other entities or open modals:
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if let Some(data) = self.pending_data.take() {
self.other_entity.update(cx, |other, cx| {
other.apply(data, window, cx);
});
}
// For modals: defer open until render
if let Some(modal) = self.pending_modal_open.take() {
self.modal.update(cx, |m, cx| m.open(modal.value, window, cx));
}
// render UI...
}Input & Actions
Event handlers: .on_click(cx.listener(|this, event, window, cx| ...))
Actions defined with actions!(namespace, [SomeAction]) macro or #[derive(Action)].
Keyboard & Mouse Patterns
Activation vs press: Use .on_click(...) for activations (open, toggle, select, run); it fires on release and gives the element an accessibility click action. Use .on_mouse_down(MouseButton::Left, ...) only for press semantics (drag start, resize grip, focus guard, backdrop dismiss, keyboard cursor sync), list each such handler in scripts/mouse_down_allowlist.txt, and check with python3 scripts/lint.py mouse-down.
Keyboard coverage: Every clickable element needs a keyboard path (a bound Command or a menu entry) registered in its surface's coverage registry. The coverage tests fail on an unregistered id. See CONTRIBUTING.md § Keyboard Coverage.
Focus tracking: Use .track_focus(&focus_handle) on container elements to receive key events:
div()
.track_focus(&self.focus_handle)
.on_key_down(cx.listener(|this, event, window, cx| { ... }))
.child(content)Mouse/keyboard sync: When a component supports both mouse and keyboard navigation, sync state on mouse events:
.on_mouse_down(MouseButton::Left, cx.listener(|this, _, _, cx| {
this.focus_mode = FocusMode::SomeMode;
this.edit_state = EditState::Editing;
cx.notify();
}))Input blur race condition: When switching between inputs via click, the old input's Blur event fires after the new input's mousedown. Use a flag to prevent focus theft:
// In mousedown handler
this.switching_input = true;
// In blur handler / exit_edit_mode
if self.switching_input {
self.switching_input = false;
return;
}Focus state machines: For complex focus scenarios (e.g., toolbar with editable inputs), use explicit state enums:
enum FocusMode { Table, Toolbar }
enum EditState { Navigating, Editing }Subscriptions
cx.subscribe(other_entity, |this, other_entity, event, cx| ...)Returns Subscription; store in _subscriptions: Vec<Subscription> field.
Deprecated Types (NEVER use)
Model<T>,View<T>→ useEntity<T>AppContext→ useAppModelContext<T>→ useContext<T>WindowContext,ViewContext<T>→ useWindow+Context<T>
Architecture Rules
Architecture details live in ARCHITECTURE.md. This file only keeps the agent-facing rules that affect how changes should be made.
UI crate split (6 crates)
The UI layer is split into six crates (see ARCHITECTURE.md § Layered crate map for the full diagram):
dbflux_components— domain-free leaf: theme, tokens, icons, primitives, composites, controls, data_table, document_tree, result_panel, chart engine, modals. Nodbflux_appdependency. May depend ondbflux_i18nfor translated UI copy;dbflux_i18nhas nodbflux_*dependencies.dbflux_ui_base— AppStateEntity, events, keymap helpers, toast, platform detection, sql_preview_modal, sso_wizard.dbflux_ui_document— tab/pane system, all document types (CodeDocument, DataDocument, ChartDocument, DashboardDocument, KeyValueDocument, AuditDocument, InstanceInspectorDocument, BucketsTableDocument, ObjectBrowserDocument, ObjectEditorDocument), data_grid_panel, governance view.dbflux_ui_sidebar— connections + scripts sidebar tree.dbflux_ui_windows— settings window and connection manager window.dbflux_ui— thin integrator: workspace, status_bar, tasks_panel, dock, remaining overlays (command_palette, login_modal, shutdown_overlay), keymap glue, assets, ipc_server, app_menu (the macOS application menu bar). Re-exports moved subsystems viapub useshims at the old module paths so internal call-sites still compile againstcrate::ui::....
dbflux_ui has no per-driver feature flags and no driver dependencies. Per-driver features live on dbflux_app (which registers drivers) and on the dbflux binary. The cross-cutting lua/aws/mcp features on UI crates only forward to dbflux_app and sibling UI crates.
Driver/UI Decoupling
Never add driver-specific logic in UI code. The UI must remain agnostic to specific database implementations.
This rule is strict and applies to both dbflux_ui and app-layer orchestration code. Do not branch on concrete driver IDs or driver names in the app/UI layer, and do not add direct references to specific drivers there unless the code is only registering/building the driver itself.
In practice, this means:
- No
if driver_id == "..."ormatch driver_idchecks indbflux_uior app-facing workflow code. - No CloudWatch/MongoDB/Redis/etc. special cases in document rendering, sidebar routing, workspace tab opening, or query-context controls.
- The core must expose the seam the UI needs, and the driver must populate or implement that seam.
- The UI may only respond to generic core abstractions such as metadata, capabilities, collection presentation hints, child-source descriptors, event-stream targets, and source-context specs.
Instead of:
// BAD: Driver-specific conditional in UI
if driver_id == "mongodb" {
show_document_view();
} else {
show_table_view();
}Use abstractions from DriverMetadata:
// GOOD: Use capability flags and metadata
match metadata.category {
DatabaseCategory::Document => show_document_view(),
DatabaseCategory::Relational => show_table_view(),
_ => show_generic_view(),
}
// GOOD: Use query language for editor behavior
let placeholder = metadata.query_language.placeholder();
let editor_mode = metadata.query_language.editor_mode();Key abstractions for UI adaptation:
DatabaseCategory: Determines view mode (table vs document tree), terminology (rows vs documents)QueryLanguage: Determines editor syntax highlighting, placeholder text, comment prefixDriverCapabilities: Determines which features to enable (pagination, transactions, etc.)CollectionPresentation: Determines how a collection/container opens (for example data grid vs event stream)ObjectFacet(viaConnection::object_facetsfor tables andConnection::collection_facetsfor collections, both default[Tabular]): Determines the view modes, gates, filter wording and grid actions inside the data document, per object. A table can carry several (PostgreSQL addsDocumentfor ajsonbcolumn,TimeSeriesfor a hypertable andVectorfor eachvector(n)column). Never derive them fromDatabaseCategoryin the UI; only the RPC adapter usescategory_collection_facetsCollectionChildInfo: Declares driver-owned child sources that appear in the sidebar without the UI inferring them from driver-specific conventionsEventStreamTarget: Lets the workspace/audit viewer open driver-backed event streams without embedding driver-specific routingRootSectionSource(viaConnection::root_section_source, gated byDriverCapabilities::ROOT_SECTIONS): Declares driver-owned object kinds at the connection root (S3 buckets), with their paginated listing, presentation and classified item actions. The sidebar renders them through one generic builder keyed by section idSourceContextSpec: Lets drivers declare extra query-context controls while the UI stays genericFormFieldKind::AuthProfileRef { provider_id: Option<String> }: Drives the connection-manager auth-profile dropdown. Drivers that need the picker declare aprofilefield asAuthProfileRef { provider_id: None }; the UI renders and field-syncs this arm generically instead of matching driver ids (DEC-1). ANonefilter enumerates built-in and external RPC-backed providers alike.
Generic Deduplication Patterns
JsonStore<T>: Single generic JSON-file store with type aliases (ProfileStore, SshTunnelStore, ProxyStore). Named constructors (.profiles(), .ssh_tunnels(), .proxies()) set the filename.
ItemManager<T>: CRUD manager with auto-save, backed by JsonStore<T>. Uses Identifiable trait for ID access and DefaultFilename trait for Default on type aliases. ProxyManager and SshTunnelManager are type aliases. ProfileManager stays separate (has extra methods like find_by_id, profile_ids).
HasSecretRef: Unifies secret operations for types with keyring references (SshTunnelProfile, ProxyProfile, AuthProfile). SecretManager generic methods (get_secret, save_secret, delete_secret) delegate through this trait.
FormGridNav<F>: 2D grid navigation for settings forms. Takes &[Vec<F>] rows as input to each method (not stored), so callers compute dynamic grids from their own state. Used by proxy and SSH tunnel settings forms.
TreeNav: Reusable tree navigation component (plain struct, not a GPUI Entity). Supports cursor movement, expand/collapse, select-by-id. Used by Settings sidebar and connections sidebar.
RPC Services Foundation
- RPC services are first-class persisted descriptors with
RpcServiceKind(Driver,AuthProvider). - Both
DriverandAuthProviderservices are active.AuthProviderservices connect throughRpcAuthProviderindbflux_ipc, which implementsDynAuthProviderfromdbflux_core. - The runtime seam for service discovery/classification lives in
dbflux_app::rpc_services; extend that boundary for future RPC capabilities instead of hardcoding new driver-only bootstrap logic inapp_state.rs. SecretProviderservices connect throughRpcSecretProviderindbflux_ipc(implementsDynSecretProvider) and are registered into every connection's value resolver; seedocs/SECRET_PROVIDER_RPC_PROTOCOL.md.- Preserve compatibility for external driver registration IDs as
rpc:<socket_id>. DynAuthProvider::fetch_dynamic_optionsis a real trait method (default implementation returnsPermanent("not supported"));RpcAuthProviderimplements it by dispatchingFetchDynamicOptionsrequests over IPC.- The auth-provider IPC protocol is at v1.3. v1.2 added the
FetchDynamicOptionsrequest /DynamicOptionsresponse variants plus thesecret_dependency_opt_inmanifest flag; v1.3 added theaudit_emit_opt_inmanifest flag and audit event emission (EmitAuditEvent). Providers advertising v1.2+ havefetch_dynamic_optionsavailable; older providers getPermanent("not supported"). - The Settings UI for Auth Profiles is provider-agnostic. To surface dynamic dropdowns a provider must declare
FormFieldKind::DynamicSelectfields in its manifest. The host strips secret field values from dependency maps unless the provider setssecret_dependency_opt_in: true. AuthSession.dataround-trips opaquely through the IPC DTO via JSON downcast and is never persisted.
Connection Hooks
- Hooks are reusable command definitions (name, command, args, cwd, env, timeout, failure policy)
- Hook execution modes are
Command,Script, andLua - Process-backed hooks can be inline or file-backed; Lua hooks run in-process through
dbflux_lua - Profile phase bindings: PreConnect, PostConnect, PreDisconnect, PostDisconnect
HookRunnerorchestrates execution withHookPhaseOutcome(success/warning/abort)- Each hook runs as its own background task with stdout/stderr visible in Tasks panel
- Process-backed hooks and
dbflux.process.run()share the same streaming executor indbflux_core; avoid duplicating process execution logic - Editor-run Lua scripts use
LuaCapabilities::all_enabled()and stream live output into a document-owned buffer via channel, not a shared mutex string - Failure policies: Disconnect (abort flow), Warn (continue with warning), Ignore (log only)
- Hooks section in Settings for global definitions; Hooks tab in Connection Manager for per-profile bindings
- Types and logic in
dbflux_core/src/connection/hook.rs, UI incrates/dbflux_ui_windows/src/settings/hooks.rsandcrates/dbflux_ui_windows/src/connection_manager/hooks_tab.rs
Adding a New Driver
- Create
crates/dbflux_driver_<name>/ - Implement
DbDriverandConnectionfromdbflux_core - Define
DriverMetadatawith appropriateDatabaseCategory,QueryLanguage, andDriverCapabilities - Define the connection form in the driver crate (e.g.
const DRIVER_FORM: DriverFormDef) and return it fromDbDriver::form_definition(). Form definitions live with the driver, not indbflux_core. - Implement
ErrorFormatterfor driver-specific error messages - Implement
QueryGeneratorwhen the driver can generate native mutation/read templates for UI previews, copy-as-query, or MCP previews- Override
Connection::object_facetswhen an object exposes more than one paradigm, and implement the matchingQueryGeneratormethod (for examplegenerate_nearest_neighborsforObjectFacet::Vector).
- Override
- Implement
LanguageServicewhen the driver speaks a non-SQL dialect (e.g.TSqlLanguageServicelives indbflux_driver_mssql). SQL drivers can reuseSqlLanguageServicefromdbflux_core. - Add feature flag in
crates/dbflux/Cargo.toml(binary) andcrates/dbflux_app/Cargo.toml. No UI crate gains a per-driver feature flag. The feature MUST also be added todefaultincrates/dbflux/Cargo.toml— release builds ship default features, so a driver left out ofdefaultsilently ships disabled. - Register in
AppState::new()under#[cfg(feature = "name")] - Register the driver's
live_integrationtest suite as its own step in the matching group of.github/workflows/tests.yml's Driver Live Integration matrix, guarded byif: matrix.group == '<group>', and add its container image to that group'simageslist — the matrix enumerates suites explicitly, so a new driver's Docker-backed tests do not run in CI until added there. - Set
ColumnMeta::kindon every column using theColumnKindenum (Timestamp, Float, Integer, Text, Unknown). The chart engine usesColumnKindexclusively — it never inspectstype_namestrings or driver identifiers. Columns withkind = Unknownare excluded from chart auto-detection. UseColumnKind::Timestampfor time columns,ColumnKind::Float/Integerfor numeric columns, andColumnKind::Textfor string columns. - Optional: implement
DashboardSourceand/orDashboardImporterand advertiseDriverCapabilities::DASHBOARD_SYNC/DASHBOARD_IMPORTto let the UI browse/import upstream dashboards (seedocs/DASHBOARDS.md). - Optional: implement
InstanceCatalog(dbflux_core/src/connection/instance_catalog.rs) and advertiseDriverCapabilities::INSTANCE_METRICS(time-series) and/orINSTANCE_INSPECTOR(tabular snapshots). The catalog exposes metrics, inspectors, aDefaultInstanceDashboarddescriptor for the read-only Instance Overview, and optionalInspectorRowActions gated by per-driver privilege probes. Seedocs/DASHBOARDS.md§ Instance Overview and inspectors. - Optional: implement
RootSectionSource(dbflux_core/src/connection/root_section/mod.rs) and advertiseDriverCapabilities::ROOT_SECTIONSfor driver-owned object kinds at the connection root. Give each item action itsExecutionClassification. Never add aSchemaNodeKindper object kind.
For external RPC-backed drivers, keep discovery/adaptation in dbflux_app::rpc_services rather than adding a parallel bootstrap path.
Driver Capabilities
Drivers declare their capabilities via DriverMetadata:
DatabaseCategory: Relational, Document, KeyValue, TimeSeries, LogStream, ObjectStorage, SecretStoreQueryLanguage: SQL, MongoQuery, RedisCommands, Cypher, etc. (determines editor syntax highlighting and placeholder)DriverCapabilities: bitflags for features (PAGINATION, TRANSACTIONS, NESTED_DOCUMENTS, etc.)ObjectFacet(dbflux_core/src/schema/facet.rs): per-object paradigms. A new paradigm lands as one facet variant, a driver that emits it and the view that renders it, in the same plan. Never add a core facet,DataStructurevariant or view kind as a skeleton to be filled in later.- A new
DatabaseCategoryalso needs its arm incategory_presentation(dbflux_components/src/category.rs), the single source of category icons, tones and container labels.
Documentation translations
docs/es/,docs/ko/anddocs/zh_Hans/mirrordocs/, the driver READMEs, and the root documents the site renders (ARCHITECTURE.md,CONTRIBUTING.md,SECURITY.md,TRADEMARK.md,PRIVACY.md).- Any edit to one of those English pages must be applied to every counterpart that already exists under
docs/es/,docs/ko/anddocs/zh_Hans/, in the same change. Do not leave a translation for a follow-up. - A page with no counterpart yet needs none; the site serves the English body with a notice.
- Keep headings, relative links, and ```mermaid fences aligned with the English page, because the site pairs pages by structure. See
docs/TRANSLATIONS.md.
Driver README documentation
- Every driver crate under
crates/dbflux_driver_*/must include aREADME.md. - Keep each driver README focused on two sections: Features and Limitations.
- Update driver README files whenever capabilities, supported operations, or known limits change.
Document System Pattern
Documents are open-tab entities managed through a closure-erasing shell. The polymorphism mechanism is PaneHandle, not a closed enum. See ARCHITECTURE.md § Document System for the full picture.
- Shell:
PaneHandle(crates/dbflux_ui_document/src/pane.rs) wraps the typedEntity<T>withBox<dyn Fn>closures covering rendering and focus, command dispatch, pure metadata reads, dedup matching, event subscription, and optional document-specific helpers such as the close and quit policy.PaneHandleis!Clone. Each document providesXxxDocument::into_pane(entity, cx) -> PaneHandlein its ownpane.rs. - Tab:
Tab::Pane(Box<PaneHandle>)(crates/dbflux_ui_document/src/tab_manager.rs) —#[non_exhaustive]single-variant enum for forward-compat. - Event: documents emit
DocumentEventdirectly (crates/dbflux_ui_document/src/handle.rs). No per-document event enums. - Dedup:
DocumentKeyenum (crates/dbflux_ui_document/src/dedup.rs) — the identity key for tab deduplication; the variant list lives in that file and is the source of truth. Find existing tabs viatab_manager.find_by_key(&DocumentKey::Table { ... }, cx). Nois_*methods. - Chrome:
ResultPanel+ViewHandle(dbflux_components::result_panel) is the universal chrome host for data-result views. View entities exposeinto_view_handle(entity, cx) -> ViewHandlewhosetoolbar_segmentsclosure returnsToolbarSegments positionedLeft | Center | Rightwithindex. Filter bars, axis bars, range chips all become segments — the chrome row usesflex_wrapso segments wrap when narrow. - Scripts: Lua/Python/Bash use
CodeDocumentand execute as scripts, not DB queries; script output streams intocrates/dbflux_ui_document/src/code/live_output.rs. - Focus: Documents receive
FocusTarget::Documentand manage internal focus via their ownFocusHandle.
Adding a new document type (zero changes to workspace/mod.rs, tab_manager.rs, tab_bar.rs, handle.rs):
- Create
crates/dbflux_ui_document/src/<name>/mod.rswith the entity - Create
crates/dbflux_ui_document/src/<name>/pane.rswithinto_pane(entity, cx) -> PaneHandle - Add a
DocumentKeyvariant incrates/dbflux_ui_document/src/dedup.rsif dedup is needed - Add
open_<name>incrates/dbflux_ui/src/ui/views/workspace/actions.rs
Known constraint: KeyValueView and LogStreamView are boundary structs in their view.rs files, NOT separate GPUI entities. impl Render remains on the host document because GPUI's single-Context<T> borrow model with cx.listener() closures over Self makes entity-level extraction infeasible without relocating all domain state. The boundary is file-level (render helpers + render code in sibling files), not entity-level.
MCP Governance System
DBFlux supports the Model Context Protocol (MCP) for AI client integration with a complete governance layer:
Classification: Operations are classified by impact level via ExecutionClassification:
Metadata— Schema introspection (list tables, describe object)Read— SELECT queries, data browsing, read-only previewsWrite— INSERT/UPDATE, mutationsDestructive— DELETE, DROP, TRUNCATEAdminSafe— Safe DDL operations (CREATE TABLE, CREATE INDEX, ADD COLUMN with default/nullable)Admin— Risky DDL operations (DROP COLUMN, RENAME COLUMN, ALTER COLUMN, DROP INDEX) and privileged admin flowsAdminDestructive— Irreversible DDL operations (DROP TABLE, DROP DATABASE, TRUNCATE TABLE)
Policy Engine (dbflux_policy):
PolicyEngine::evaluate()takes actor, connection, tool, and classification- Returns
PolicyDecision::AlloworPolicyDecision::Deny(reason) - Supports roles with policy composition and connection-scoped assignments
TrustedClientRegistryidentifies known AI clients
Approval Flow (dbflux_approval):
- Destructive or write operations can require human approval
InMemoryPendingExecutionStoreholds deferred executionsApprovalServicemanages approve/reject lifecycle
Audit (dbflux_audit):
- SQLite-backed audit log in
~/.local/share/dbflux/dbflux.db(aud_audit_eventstable) - Events use the
EventRecordtype fromdbflux_core::observabilitywith category, severity, outcome, actor type, and structured fields - Events are emitted through the
EventSinktrait — injectArc<dyn EventSink>into service layers rather than callingAuditServicedirectly - Categories:
Query,Connection,Hook,Script,Mcp,Governance,Config,System - By default: sensitive values are redacted, query text is replaced with a SHA256 fingerprint (not stored in full), details_json is capped at 64 KiB
- Queryable via
AuditQueryFilter(actor, tool, category, action, outcome, date range, free text) - Export to JSON/CSV via
AuditService::export()(basic) orexport_extended()(all fields including details_json) - Purge old events by retention policy:
AuditService::purge_old_events(days, batch_size) - See
docs/AUDIT.mdfor the full event schema, required fields per category, and usage patterns
Runtime (dbflux_mcp):
McpRuntimeimplementsMcpGovernanceServicetrait- Integrates policy engine, approval service, and audit service
- Emits
McpRuntimeEventfor UI updates - Tool catalog defines canonical MCP tools and deferred tools
Important runtime rules:
preview_mutationmust stay read-only; it may return generated SQL/query text or a non-mutating plan, but must never execute the mutation being previewedpreview_ddlis intentionally not exposed from the MCP surface until DBFlux has a truly safe schema-preview pathselect_datarunsjoinsonly on a connection whose driver declaresQueryCapabilities::supports_joinsand whoseQueryGenerator::generate_selectrenders the spec; every other connection gets an explicit error, never a result with the join left out. Theoncondition is parsed into structured column comparisons (parse_join_conditionindbflux_core), table, alias and column names must be plain identifiers, and the generated query must classify as a read before it runs. Never buildJoinOn::RawExpressionfrom tool input
Trust model:
MCP authentication is process-identity only: presenting --client-id is the sole authentication signal. Any local process that knows the client ID can connect. This is not a cryptographic guarantee. Do not expose the MCP server beyond localhost without an additional authentication layer. A cryptographic MCP auth layer is a known follow-up item, not yet scheduled.
Standalone Server (dbflux_mcp_server):
- Integrated as subcommand:
dbflux mcp --client-id <id>for AI clients - Communicates via JSON-RPC over stdin/stdout
- Uses same governance stack as in-app MCP
- Optional: Can be disabled with
--no-default-featuresat build time
UI Integration:
McpApprovalsViewdocument for reviewing pending executions- MCP settings section for trusted clients, roles, and policies
AuditDocumentas the unified audit viewer for all event categories (no separate MCP audit surface)LoginModalandSsoWizardoverlays for AWS SSO authentication flow
Dashboards & Saved Charts
- Saved charts and dashboards are persisted in SQLite under the
viz_*table prefix (viz_dashboards,viz_dashboard_panels,viz_saved_charts,viz_saved_chart_series,viz_saved_chart_binding_y,viz_saved_chart_source_metric_*). Repositories live incrates/dbflux_storage/src/repositories/viz_*.rs. - In-memory managers wrap the repositories:
DashboardManager(crates/dbflux_ui_base/src/dashboard_manager.rs) forDashboard/DashboardPanel/DashboardPanelKind { Chart { saved_chart_id } | Divider { markdown } | Inspector { metric_id } }, andSavedChartManager(crates/dbflux_ui_base/src/saved_chart_manager.rs) forSavedChart+SavedChartRefreshPolicy. Writes go to the repo first; caches update only on success. DashboardDocument(crates/dbflux_ui_document/src/dashboard/) hosts a 12-column grid of chart, divider, and inspector panels with a sharedTimeRangePanel. Dedup keys:DocumentKey::Dashboard { dashboard_id }(persisted) orDocumentKey::InstanceOverview { profile_id }(auto-generated read-only Instance Overview).- Refresh timers (dashboard, standalone chart, inspector) check
AppState::connections()before each tick and skip work when the underlying profile is disconnected; the timer itself stays alive so refresh resumes on reconnect without re-arming. - Driver seams (UI never branches on driver id):
DashboardSource(dbflux_core/src/connection/dashboard_source.rs) — lists upstream dashboards; gated byDriverCapabilities::DASHBOARD_SYNC.DashboardImporter(dbflux_core/src/connection/dashboard_import.rs) — parses upstream JSON intoWidgetImportSpecs; gated byDriverCapabilities::DASHBOARD_IMPORT.InstanceCatalog(dbflux_core/src/connection/instance_catalog.rs) — exposes per-driver metrics, inspectors, default-dashboard descriptor, and row actions; gated byDriverCapabilities::INSTANCE_METRICS/INSTANCE_INSPECTOR.- CloudWatch is the reference implementation for
DashboardSource/DashboardImporter. PostgreSQL, MySQL/MariaDB, MongoDB, Redis, SQL Server, ClickHouse, and InfluxDB (v2 only) are the reference implementations forInstanceCatalog.
- Remote dashboard listings are session-scoped via
RemoteDashboardCache(crates/dbflux_app/src/remote_dashboard_cache.rs); they do not persist across restart.
Full reference: docs/DASHBOARDS.md.
Visual Query Builder
The right-rail builder composes SELECT / UPDATE / DELETE without writing SQL. It is driver-agnostic: gated on QueryLanguage::Sql with no per-driver branching, so every relational driver gets it.
- Specs live in
dbflux_core/src/query/visual_query.rs(re-exported fromdbflux_core::query):VisualQuerySpec,VisualMutationSpec, andEditableBinding. - SQL generation extends the existing
QueryGeneratortrait (dbflux_core/src/query/generator.rs) with defaultedgenerate_select/generate_update_from_spec/generate_delete_from_spec;SqlSelectBuilderrenders the dialect-specific SQL (SQLite, PostgreSQL, MySQL/MariaDB, SQL Server). Add new builder shapes here, not in the UI. MutationPolicy(dbflux_core/src/connection/manager.rs) composes toAllowed/ReadOnly/ApprovalRequired.MutationExecutor(crates/dbflux_ui_document/src/data_grid_panel/mutation_executor.rs) runs the chosen mode:SingleTransaction,ChunkedTransaction(keyset over the PK), orDirectAutocommit.- No-
WHEREUPDATE/DELETE is gated by the shared dangerous-query dispatcher (see Language Services), not a builder-local check. - UI lives in
crates/dbflux_ui_document/src/query_builder/(panel, view,sections/,mutation_state,completion,events) and integrates into the DataView throughcrates/dbflux_ui_document/src/data_grid_panel/. - Inline edit on builder-generated SELECT results is driven by
EditableBinding: the result must be editable-safe (maps 1:1 to one table with every PK column projected under its original name); otherwise the grid is read-only. The proof lives indbflux_coreover generic spec/metadata types — keep it there, not indbflux_ui. - Persistence: migration
017_qry_saved_queries, theqry_*tables,SavedQueryRepo(crates/dbflux_storage/src/repositories/qry_saved_queries.rs), and the in-memorySavedQueryManager(crates/dbflux_ui_base/src/saved_query_manager.rs) wired intoAppStateEntity. Cross-connection import verifies table existence through aTableProbeseam rather than reaching into driver code.
The document query builder is a separate rail for document collections, with the same rules:
- The spec is
DocumentQuerySpecindbflux_core/src/query/document_query.rs. Drivers implementDocumentQueryCodecand return it fromConnection::document_query_codec(); the codec renders a spec as find slots, a pipeline and preview text, and parses the slots back with the clauses it cannot represent. - The UI gates on
DocumentFeatures::VISUAL_BUILDER(andDocumentFeatures::AGGREGATEfor Aggregate mode), never on a driver id. It lives incrates/dbflux_ui_document/src/document_builder/anddata_grid_panel/documents/builder/mod.rs. - Execution goes through the query slots and
browse_collection(Find) or the Aggregate view (Aggregate); the builder never executes generated shell text. - Persistence: migration
037_qry_saved_document_queries,DocumentQueryRepo(crates/dbflux_storage/src/repositories/qry_saved_document_queries.rs) andSavedDocumentQueryManager(crates/dbflux_ui_base/src/saved_document_query_manager.rs).
Language Services
LanguageServicetrait incrates/dbflux_core/src/query/language_service.rsexposesvalidate,detect_dangerous, andeditor_diagnostics.SqlLanguageServiceis the default impl for relational drivers.- Non-SQL dialects ship their
LanguageServicefrom the driver crate (e.g.TSqlLanguageServicelives indbflux_driver_mssql; MongoDB and Redis dangerous detection live in their own driver crates and route throughclassify_query_for_language(&QueryLanguage, &str)). DangerousQueryKindenumerates risky patterns across SQL (DeleteNoWhere,UpdateNoWhere,Truncate,Drop,Alter,Script), MongoDB (deleteMany,updateMany,dropCollection,dropDatabase), and Redis (FlushAll,FlushDb,MultiDelete,KeysPattern).- The UI must call into the dispatcher; do NOT add per-driver dangerous-query branches in
dbflux_ui.
WHERE Clause Syntax
DBFlux MCP uses a unified JSON WHERE clause syntax that works across all database drivers (SQL, MongoDB, Redis, DynamoDB):
ColumnRef Pattern: Column references support three forms:
ColumnRef::Name("email")— Simple column referenceColumnRef::Nested(vec!["metadata", "profile", "age"])— Nested document field (MongoDB, JSONB)ColumnRef::JsonPath { column: "config", path: "$.notifications.email" }— JSON path syntax
Operators: Standard comparison ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin), pattern matching ($like, $ilike, $regex), NULL handling (null, $eq: null), existence ($exists), array operations ($contains, $overlap, $size, $all), and logical composition ($and, $or, $not).
Type Coercion: Automatic type conversion (string ↔ number ↔ boolean) with validation.
Driver Translation: WHERE clauses translate to SQL WHERE, MongoDB query filters, Redis SCAN patterns, or DynamoDB FilterExpression.
Reference: See crates/dbflux_mcp_server/docs/WHERE_CLAUSE_SYNTAX.md for complete syntax guide with examples.
DDL Preview System
MCP provides a preview-before-execute workflow for schema changes:
Preview Workflow:
- AI agent calls
preview_mutationwith operation parameters - DBFlux generates SQL/query text or an execution preview using driver-owned generation/planning
- Preview returned with SQL, classification, affected objects, and warnings
- Agent reviews and decides whether to proceed
- Agent calls actual tool (
alter_table,create_table, etc.) if safe
Current limitation:
- DDL preview is not exposed as a standalone MCP tool in this branch. The old
preview_ddlsurface was removed because it could not guarantee a non-mutating preview across drivers.
Classification Algorithm: classify_alter_table_operation() in dbflux_core/src/query/classify.rs determines risk level:
ADD COLUMN(nullable or with default) →AdminSafeADD COLUMN(non-nullable without default) →Admin(requires backfill)DROP COLUMN,RENAME COLUMN,ALTER COLUMN→AdminADD CONSTRAINT(validation) →AdminSafeADD CONSTRAINT(FK with CASCADE DELETE) →AdminDROP CONSTRAINT,DROP INDEX→AdminDROP TABLE,TRUNCATE TABLE,DROP DATABASE→AdminDestructive
ALTER TABLE Safety Rules:
- Safe operations:
ADD COLUMN(nullable),CREATE INDEX, validation constraints - Risky operations:
DROP COLUMN(data loss),RENAME COLUMN(app breakage),ALTER COLUMN(type change) - Destructive operations:
DROP TABLE,TRUNCATE TABLE
Driver-Specific Behavior:
- PostgreSQL: All DDL is transactional (except
CREATE INDEX CONCURRENTLY) - MySQL: DDL is NOT transactional; rewrites entire table for most
ALTER TABLEops - SQLite: Limited
ALTER TABLEsupport (onlyADD COLUMN,RENAME);DROP COLUMNrequires table recreation - SQL Server (MSSQL): DDL is transactional (can run inside a transaction and roll back)
Reference: See crates/dbflux_mcp_server/docs/DDL_SAFETY.md for complete safety guide with classification matrix.
Platform Detection
crates/dbflux_ui_base/src/platform.rs handles X11/Wayland differences:
floating_window_kind()returnsWindowKind::Floatingon every platform. On Linux, gpui parents a floating window to the window holding keyboard focus when it opens (xdg_toplevel.set_parenton Wayland,WM_TRANSIENT_FORon X11), so tiling compositors float it at its requested size. Do not useWindowKind::Dialogfor secondary windows: it is modal and blocks input to the parent.apply_window_options()sets min size so X11 WMs emitWM_NORMAL_HINTS
A pub use dbflux_ui_base::platform::* shim remains at crates/dbflux_ui/src/platform.rs for internal compatibility.
Release Channels & Branding
ReleaseChannel(crates/dbflux_core/src/release_channel.rs) derives the channel (Stable/Rc/Nightly) once from the compiledCARGO_PKG_VERSION. CI stamps the version before building, so the channel is baked into the binary.- Read channel identity through the accessors —
app_id(),display_name(),db_file_name()— never branch on the raw version string and never hardcodedbflux/dbflux-nightlyidentifiers. Nightly returns a distinctapp_idand DB file so it coexists with stable;Rcshares stable's identity. - The channel-aware DB path lives in
crates/dbflux_storage/src/paths.rs(dbflux_db_path); nightly can opt into the stable DB viaset_nightly_shares_stable_db/nightly_shares_stable_db. - Brand assets live under
resources/branding/{stable,nightly}/(served per channel incrates/dbflux_ui/src/assets.rs). Packaging and Nix files substitute channel placeholders. The release/nightly flow is documented indocs/RELEASE.md.
Website
The site lives in web/ and is an Astro static build. It is not a crate: cargo excludes it, and
the Rust workflows skip changes confined to it or to markdown.
It renders the repository's own documentation. docs/*.md, the driver READMEs,
ARCHITECTURE.md and CONTRIBUTING.md are read straight out of git at build time. There is no
second copy, so a behaviour change and the paragraph describing it belong in the same commit, and
editing a document is all that is needed to change the site.
It publishes several versions. web/versions.json lists them; each entry is a git ref, and the
product version shown for it is read from that ref's Cargo.toml rather than typed. The first
entry is the current release and is served unprefixed at /docs/; the rest are served under their
id (/v0.6/docs/, /nightly/docs/) and keep those URLs. Granularity is the minor series, not the
patch, because a release branch takes cherry-picked fixes only.
Rules that matter when touching documentation:
- Write markdown that reads correctly on GitHub. Relative links between documents and labels that
name a file are rewritten to site routes at build time, so
[Settings](SETTINGS.md)works in both places. - Diagrams go in ```mermaid fences. They render as diagrams on the site; ASCII art does not.
Screenshots live in
docs/images/<page>/as WebP, one light and one dark file, and every locale shares them:images/...fromdocs/,../images/...from a translation. Embed them with<picture>so GitHub and the site both pick the theme's image:<picture> <source media="(prefers-color-scheme: dark)" srcset="images/usage/main-window-dark.webp"> <img src="images/usage/main-window-light.webp" alt="The main window with a PostgreSQL table open"> </picture>- Adding a document to
docs/gives it a page automatically. Its place in the reading order is declared inweb/src/data/nav.ts; unlisted documents still get a page and appear under "Not yet filed" on the documentation index. - Never send a reader to the repository for something the site renders. Link to the page.
cd web
pnpm install
pnpm dev # local server
pnpm build # static output in web/dist
pnpm check # types — this is a real gate, not decoration
pnpm format # prettierWhere the documentation is served
DOCS_MODE decides, and it has three values:
| Value | What it builds | Documentation URL |
embedded (default) | everything, one origin | /docs/usage/ |
site | landing pages only | https://docs.dbflux.dev/usage/ |
docs | the documentation, at the root of its own host | /usage/ |
Local development uses the default, so pnpm dev still brings up the whole site
with one command. A split deployment is two builds of this same source:
pnpm build:site # DOCS_MODE=site
pnpm build:docs # DOCS_MODE=docsOverride SITE_ORIGIN and DOCS_ORIGIN if the hostnames change. Nothing else
in the source knows a URL: pages link through docsUrl() and siteUrl(), which
is what lets one variable move an entire section of the site.
Neither host serves a page that belongs to the other. A split build writes a
Cloudflare _redirects file (web/src/integrations/host-redirects.ts) so every
/docs/... URL the site once published answers 301 to the documentation host,
and /about/ on the documentation host answers 301 back. A moved page must
answer with a status code rather than a 200 carrying a meta refresh: a 200 at a
URL that moved reads, to a crawler or an agent, as a site still being
rearranged.
web/scripts/check-links.mjs runs in CI after the build and fails on an internal link that points
at a page which is not built. Versions differ, so a link that resolves in nightly may not resolve
in an older release.
Two caches will mislead you. Astro keeps rendered markdown in web/.astro and
web/node_modules/.astro, so a change to the markdown pipeline can appear to do nothing — use
pnpm clean. And astro dev and astro preview are daemons that outlive the shell and silently
redirect to an existing instance instead of starting a new one, so a stale server can serve an old
build long after a rebuild; use pnpm stop.
Common Pitfalls
- Forgetting
cx.notify()after state changes - Blocking UI thread — use
background_executor().spawn()for DB ops - Entity updates in render loops — guard with
.take() - Missing feature gates on driver code
- Creating closures per cell in tables — use row-level handlers with hit-testing instead
- Canvas re-rendering every frame — cache scroll state and only sync on meaningful changes
- Audit event validation —
EventCategory::Configrequiressummary,object_type, ANDobject_id; missing any causes silent failure invalidate_event()
For key files and the cross-crate map, see ARCHITECTURE.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

