agentleFS
0xErwin10xErwin1/dbflux/AGENTS.md

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

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
  1. Very specific:

    Use only labels from the taxonomy in CONTRIBUTING.md § Label Guide, and never invent labels such as area:*.

    line 21
  2. Very specific:

    Launching DBFlux for a session: Always isolate the data directories, so the session never reads or writes the user's real configuration, …

    line 95
  3. Very specific:

    Wait for the result with wait_for_element or wait_for_state instead of sleeping.

    line 114
  4. Very specific:

    Check the outcome in the element tree, and with screenshot when the change is visual.

    line 116
  5. Very specific:

    Stop the DBFlux process when the session ends, and never commit screenshots or recordings.

    line 130
  6. Very specific:

    Never add ui-automation to a default feature list.

    line 132
  7. Distinctive:

    Do not write comments that summarize code.

    line 139
  8. Very specific:

    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.

    line 141
  9. Distinctive:

    Keep functions under ~100 lines.

    line 142
  10. Distinctive:

    Avoid creative additions unless explicitly requested

    line 143
  11. Distinctive:

    Use full words for variable names (no abbreviations like "q" for "queue")

    line 144
  12. Very specific:

    Avoid unwrap() and functions that panic.

    line 148
  13. Very specific:

    If you genuinely want to drop a value — not an error — bind it to _name instead of _.

    line 155
  14. Distinctive:

    Ensure async errors propagate to the UI so users get meaningful feedback.

    line 156
  15. Very specific:

    Use mod.rs for module directories (e.g., views/mod.rs, not a sibling views.rs)

    line 170
  16. Very specific:

    When creating crates, specify library root in Cargo.toml with [lib] path = "..."

    line 171
  17. Distinctive:

    Use variable shadowing to scope clones in async contexts:

    line 175
  18. Very specific:

    Use the inner cx inside closures, not the outer one, to avoid multiple borrows.

    line 224
  19. Distinctive:

    All entity/UI work happens on the foreground thread.

    line 228
  20. Very specific:

    Use .when(condition, |this| ...) for conditional attributes/children

    line 265
  21. Very specific:

    Use .when_some(option, |this, value| ...) for Option-based conditionals

    line 266
  22. Very specific:

    Call cx.notify() when state changes affect rendering

    line 267
  23. Very specific:

    Every window must render under gpui_component::Root, which sets the rem size from the UI font size.

    line 272
  24. Very specific:

    Do not write text_size(px(..)).

    line 275
  25. Very specific:

    Use pending_* fields with .take() to safely update other entities or open modals:

    line 279
  26. Very specific:

    Keyboard coverage: Every clickable element needs a keyboard path (a bound Command or a menu entry) registered in its surface's coverage …

    line 306
  27. Very specific:

    Focus tracking: Use .track_focus(&focus_handle) on container elements to receive key events:

    line 308
  28. Distinctive:

    Mouse/keyboard sync: When a component supports both mouse and keyboard navigation, sync state on mouse events:

    line 317
  29. Very specific:

    Input blur race condition: When switching between inputs via click, the old input's Blur event fires after the new input's mousedown.

    line 327
  30. Very specific:

    No CloudWatch/MongoDB/Redis/etc.

    line 388
  31. Very specific:

    Use abstractions from DriverMetadata:

    line 403
  32. Very specific:

    Add feature flag in crates/dbflux/Cargo.toml (binary) and crates/dbflux_app/Cargo.toml.

    line 479
  33. Very specific:

    Keep headings, relative links, and ``mermaid fences aligned with the English page, because the site pairs pages by structure.

    line 504
  34. Very specific:

    Every driver crate under crates/dbflux_driver_*/ must include a README.md.

    line 508
  35. Distinctive:

    Keep each driver README focused on two sections: Features and Limitations.

    line 509
  36. Distinctive:

    Update driver README files whenever capabilities, supported operations, or known limits change.

    line 510
  37. Very specific:

    Create crates/dbflux_ui_document/src/<name>/mod.rs with the entity

    line 525
  38. Very specific:

    Create crates/dbflux_ui_document/src/<name>/pane.rs with into_pane(entity, cx) -> PaneHandle

    line 526
  39. Very specific:

    Add a DocumentKey variant in crates/dbflux_ui_document/src/dedup.rs if dedup is needed

    line 527
  40. Very specific:

    Add open_<name> in crates/dbflux_ui/src/ui/views/workspace/actions.rs

    line 528
  41. Very specific:

    Read channel identity through the accessors — app_id(), display_name(), db_file_name() — never branch on the raw version string and never …

    line 699
  42. Very specific:

    Write markdown that reads correctly on GitHub.

    line 721
  43. Distinctive:

    Never send a reader to the repository for something the site renders.

    line 739

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

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.yml for a defect, feature_request.yml for a capability. Fill the form's fields in the order it declares them, and keep the [bug] / [feature] title prefix and the Labels I plan to apply checklist it renders.
  • A pull request uses .github/pull_request_template.md verbatim: 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, gh commands, reading the full branch delta) live in skills/issue-creation/SKILL.md and skills/branch-pr/SKILL.md.
  • Open the issue first and wait for maintainer approval (the status:approved label) 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 from main rather than long stacks (see CONTRIBUTING.md § Pull Requests).
  • Use only labels from the taxonomy in CONTRIBUTING.md § Label Guide, and never invent labels such as area:*.
  • A contributor without write access to 0xErwin1/dbflux cannot 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 directly

scripts/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 dbflux

Launching 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-automation

Working loop.

  1. Find the target with get_ui_tree or find_elements. Elements are addressed by their GPUI element id (.id("rail-scripts") becomes rail-scripts).
  2. Act with click_element, type_text, set_text or keyboard (GPUI keystroke syntax, for example ctrl-shift-p).
  3. Wait for the result with wait_for_element or wait_for_state instead of sleeping.
  4. Check the outcome in the element tree, and with screenshot when 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 from form_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-automation to a default feature 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.rs plus submodules) by responsibility. New files belong inside the module directory they serve, not loose next to unrelated files.
  • 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.
  • 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 discarding Result / Option errors 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() (from dbflux_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 over log::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 _name instead of _. Reserve the bare let _ = pattern for that intent only.
  • 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's ErrorFormatter output feeds cause directly — keeps the UI driver-agnostic.
  • ErrorKind variants: 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.rs for module directories (e.g., views/mod.rs, not a sibling views.rs)
  • When creating crates, specify library root in Cargo.toml with [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, indexes

Driver 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 access
  • Context<T> — provided when updating Entity<T>, derefs to App
  • AsyncApp / AsyncWindowContext — from cx.spawn, can cross await points
  • Window — window state, passed before cx when present

Entity Operations

With thing: Entity<T>:

  • thing.read(cx) → &T
  • thing.update(cx, |thing, cx| ...) → mutate with Context<T>
  • thing.update_in(cx, |thing, window, cx| ...) → also provides Window

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() or task.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 stay Pixels.
  • 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()), or fonts::ui_px(cx, token) without a Window.
  • The data grid and the code editor follow their own font sizes: use fonts::grid_* / fonts::grid_px and fonts::editor_* / fonts::editor_scaled, not tokens::ui.
  • Do not write text_size(px(..)). The dbflux_components style 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> → use Entity<T>
  • AppContext → use App
  • ModelContext<T> → use Context<T>
  • WindowContext, ViewContext<T> → use Window + 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. No dbflux_app dependency. May depend on dbflux_i18n for translated UI copy; dbflux_i18n has no dbflux_* 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 via pub use shims at the old module paths so internal call-sites still compile against crate::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 == "..." or match driver_id checks in dbflux_ui or 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 prefix
  • DriverCapabilities: Determines which features to enable (pagination, transactions, etc.)
  • CollectionPresentation: Determines how a collection/container opens (for example data grid vs event stream)
  • ObjectFacet (via Connection::object_facets for tables and Connection::collection_facets for 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 adds Document for a jsonb column, TimeSeries for a hypertable and Vector for each vector(n) column). Never derive them from DatabaseCategory in the UI; only the RPC adapter uses category_collection_facets
  • CollectionChildInfo: Declares driver-owned child sources that appear in the sidebar without the UI inferring them from driver-specific conventions
  • EventStreamTarget: Lets the workspace/audit viewer open driver-backed event streams without embedding driver-specific routing
  • RootSectionSource (via Connection::root_section_source, gated by DriverCapabilities::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 id
  • SourceContextSpec: Lets drivers declare extra query-context controls while the UI stays generic
  • FormFieldKind::AuthProfileRef { provider_id: Option<String> }: Drives the connection-manager auth-profile dropdown. Drivers that need the picker declare a profile field as AuthProfileRef { provider_id: None }; the UI renders and field-syncs this arm generically instead of matching driver ids (DEC-1). A None filter 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 Driver and AuthProvider services are active. AuthProvider services connect through RpcAuthProvider in dbflux_ipc, which implements DynAuthProvider from dbflux_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 in app_state.rs.
  • SecretProvider services connect through RpcSecretProvider in dbflux_ipc (implements DynSecretProvider) and are registered into every connection's value resolver; see docs/SECRET_PROVIDER_RPC_PROTOCOL.md.
  • Preserve compatibility for external driver registration IDs as rpc:<socket_id>.
  • DynAuthProvider::fetch_dynamic_options is a real trait method (default implementation returns Permanent("not supported")); RpcAuthProvider implements it by dispatching FetchDynamicOptions requests over IPC.
  • The auth-provider IPC protocol is at v1.3. v1.2 added the FetchDynamicOptions request / DynamicOptions response variants plus the secret_dependency_opt_in manifest flag; v1.3 added the audit_emit_opt_in manifest flag and audit event emission (EmitAuditEvent). Providers advertising v1.2+ have fetch_dynamic_options available; older providers get Permanent("not supported").
  • The Settings UI for Auth Profiles is provider-agnostic. To surface dynamic dropdowns a provider must declare FormFieldKind::DynamicSelect fields in its manifest. The host strips secret field values from dependency maps unless the provider sets secret_dependency_opt_in: true.
  • AuthSession.data round-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, and Lua
  • Process-backed hooks can be inline or file-backed; Lua hooks run in-process through dbflux_lua
  • Profile phase bindings: PreConnect, PostConnect, PreDisconnect, PostDisconnect
  • HookRunner orchestrates execution with HookPhaseOutcome (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 in dbflux_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 in crates/dbflux_ui_windows/src/settings/hooks.rs and crates/dbflux_ui_windows/src/connection_manager/hooks_tab.rs

Adding a New Driver

  1. Create crates/dbflux_driver_<name>/
  2. Implement DbDriver and Connection from dbflux_core
  3. Define DriverMetadata with appropriate DatabaseCategory, QueryLanguage, and DriverCapabilities
  4. Define the connection form in the driver crate (e.g. const DRIVER_FORM: DriverFormDef) and return it from DbDriver::form_definition(). Form definitions live with the driver, not in dbflux_core.
  5. Implement ErrorFormatter for driver-specific error messages
  6. Implement QueryGenerator when the driver can generate native mutation/read templates for UI previews, copy-as-query, or MCP previews
    • Override Connection::object_facets when an object exposes more than one paradigm, and implement the matching QueryGenerator method (for example generate_nearest_neighbors for ObjectFacet::Vector).
  7. Implement LanguageService when the driver speaks a non-SQL dialect (e.g. TSqlLanguageService lives in dbflux_driver_mssql). SQL drivers can reuse SqlLanguageService from dbflux_core.
  8. Add feature flag in crates/dbflux/Cargo.toml (binary) and crates/dbflux_app/Cargo.toml. No UI crate gains a per-driver feature flag. The feature MUST also be added to default in crates/dbflux/Cargo.toml — release builds ship default features, so a driver left out of default silently ships disabled.
  9. Register in AppState::new() under #[cfg(feature = "name")]
  10. Register the driver's live_integration test suite as its own step in the matching group of .github/workflows/tests.yml's Driver Live Integration matrix, guarded by if: matrix.group == '<group>', and add its container image to that group's images list — the matrix enumerates suites explicitly, so a new driver's Docker-backed tests do not run in CI until added there.
  11. Set ColumnMeta::kind on every column using the ColumnKind enum (Timestamp, Float, Integer, Text, Unknown). The chart engine uses ColumnKind exclusively — it never inspects type_name strings or driver identifiers. Columns with kind = Unknown are excluded from chart auto-detection. Use ColumnKind::Timestamp for time columns, ColumnKind::Float/Integer for numeric columns, and ColumnKind::Text for string columns.
  12. Optional: implement DashboardSource and/or DashboardImporter and advertise DriverCapabilities::DASHBOARD_SYNC / DASHBOARD_IMPORT to let the UI browse/import upstream dashboards (see docs/DASHBOARDS.md).
  13. Optional: implement InstanceCatalog (dbflux_core/src/connection/instance_catalog.rs) and advertise DriverCapabilities::INSTANCE_METRICS (time-series) and/or INSTANCE_INSPECTOR (tabular snapshots). The catalog exposes metrics, inspectors, a DefaultInstanceDashboard descriptor for the read-only Instance Overview, and optional InspectorRowActions gated by per-driver privilege probes. See docs/DASHBOARDS.md § Instance Overview and inspectors.
  14. Optional: implement RootSectionSource (dbflux_core/src/connection/root_section/mod.rs) and advertise DriverCapabilities::ROOT_SECTIONS for driver-owned object kinds at the connection root. Give each item action its ExecutionClassification. Never add a SchemaNodeKind per 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, SecretStore
  • QueryLanguage: 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, DataStructure variant or view kind as a skeleton to be filled in later.
  • A new DatabaseCategory also needs its arm in category_presentation (dbflux_components/src/category.rs), the single source of category icons, tones and container labels.

Documentation translations

  • docs/es/, docs/ko/ and docs/zh_Hans/ mirror docs/, 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/ and docs/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 a README.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.

  1. Shell: PaneHandle (crates/dbflux_ui_document/src/pane.rs) wraps the typed Entity<T> with Box<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. PaneHandle is !Clone. Each document provides XxxDocument::into_pane(entity, cx) -> PaneHandle in its own pane.rs.
  2. Tab: Tab::Pane(Box<PaneHandle>) (crates/dbflux_ui_document/src/tab_manager.rs) — #[non_exhaustive] single-variant enum for forward-compat.
  3. Event: documents emit DocumentEvent directly (crates/dbflux_ui_document/src/handle.rs). No per-document event enums.
  4. Dedup: DocumentKey enum (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 via tab_manager.find_by_key(&DocumentKey::Table { ... }, cx). No is_* methods.
  5. Chrome: ResultPanel + ViewHandle (dbflux_components::result_panel) is the universal chrome host for data-result views. View entities expose into_view_handle(entity, cx) -> ViewHandle whose toolbar_segments closure returns ToolbarSegments positioned Left | Center | Right with index. Filter bars, axis bars, range chips all become segments — the chrome row uses flex_wrap so segments wrap when narrow.
  6. Scripts: Lua/Python/Bash use CodeDocument and execute as scripts, not DB queries; script output streams into crates/dbflux_ui_document/src/code/live_output.rs.
  7. Focus: Documents receive FocusTarget::Document and manage internal focus via their own FocusHandle.

Adding a new document type (zero changes to workspace/mod.rs, tab_manager.rs, tab_bar.rs, handle.rs):

  1. Create crates/dbflux_ui_document/src/<name>/mod.rs with the entity
  2. Create crates/dbflux_ui_document/src/<name>/pane.rs with into_pane(entity, cx) -> PaneHandle
  3. Add a DocumentKey variant in crates/dbflux_ui_document/src/dedup.rs if dedup is needed
  4. Add open_<name> in crates/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 previews
  • Write — INSERT/UPDATE, mutations
  • Destructive — DELETE, DROP, TRUNCATE
  • AdminSafe — 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 flows
  • AdminDestructive — Irreversible DDL operations (DROP TABLE, DROP DATABASE, TRUNCATE TABLE)

Policy Engine (dbflux_policy):

  • PolicyEngine::evaluate() takes actor, connection, tool, and classification
  • Returns PolicyDecision::Allow or PolicyDecision::Deny(reason)
  • Supports roles with policy composition and connection-scoped assignments
  • TrustedClientRegistry identifies known AI clients

Approval Flow (dbflux_approval):

  • Destructive or write operations can require human approval
  • InMemoryPendingExecutionStore holds deferred executions
  • ApprovalService manages approve/reject lifecycle

Audit (dbflux_audit):

  • SQLite-backed audit log in ~/.local/share/dbflux/dbflux.db (aud_audit_events table)
  • Events use the EventRecord type from dbflux_core::observability with category, severity, outcome, actor type, and structured fields
  • Events are emitted through the EventSink trait — inject Arc<dyn EventSink> into service layers rather than calling AuditService directly
  • 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) or export_extended() (all fields including details_json)
  • Purge old events by retention policy: AuditService::purge_old_events(days, batch_size)
  • See docs/AUDIT.md for the full event schema, required fields per category, and usage patterns

Runtime (dbflux_mcp):

  • McpRuntime implements McpGovernanceService trait
  • Integrates policy engine, approval service, and audit service
  • Emits McpRuntimeEvent for UI updates
  • Tool catalog defines canonical MCP tools and deferred tools

Important runtime rules:

  • preview_mutation must stay read-only; it may return generated SQL/query text or a non-mutating plan, but must never execute the mutation being previewed
  • preview_ddl is intentionally not exposed from the MCP surface until DBFlux has a truly safe schema-preview path
  • select_data runs joins only on a connection whose driver declares QueryCapabilities::supports_joins and whose QueryGenerator::generate_select renders the spec; every other connection gets an explicit error, never a result with the join left out. The on condition is parsed into structured column comparisons (parse_join_condition in dbflux_core), table, alias and column names must be plain identifiers, and the generated query must classify as a read before it runs. Never build JoinOn::RawExpression from 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-features at build time

UI Integration:

  • McpApprovalsView document for reviewing pending executions
  • MCP settings section for trusted clients, roles, and policies
  • AuditDocument as the unified audit viewer for all event categories (no separate MCP audit surface)
  • LoginModal and SsoWizard overlays 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 in crates/dbflux_storage/src/repositories/viz_*.rs.
  • In-memory managers wrap the repositories: DashboardManager (crates/dbflux_ui_base/src/dashboard_manager.rs) for Dashboard / DashboardPanel / DashboardPanelKind { Chart { saved_chart_id } | Divider { markdown } | Inspector { metric_id } }, and SavedChartManager (crates/dbflux_ui_base/src/saved_chart_manager.rs) for SavedChart + 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 shared TimeRangePanel. Dedup keys: DocumentKey::Dashboard { dashboard_id } (persisted) or DocumentKey::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 by DriverCapabilities::DASHBOARD_SYNC.
    • DashboardImporter (dbflux_core/src/connection/dashboard_import.rs) — parses upstream JSON into WidgetImportSpecs; gated by DriverCapabilities::DASHBOARD_IMPORT.
    • InstanceCatalog (dbflux_core/src/connection/instance_catalog.rs) — exposes per-driver metrics, inspectors, default-dashboard descriptor, and row actions; gated by DriverCapabilities::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 for InstanceCatalog.
  • 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 from dbflux_core::query): VisualQuerySpec, VisualMutationSpec, and EditableBinding.
  • SQL generation extends the existing QueryGenerator trait (dbflux_core/src/query/generator.rs) with defaulted generate_select / generate_update_from_spec / generate_delete_from_spec; SqlSelectBuilder renders 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 to Allowed / ReadOnly / ApprovalRequired. MutationExecutor (crates/dbflux_ui_document/src/data_grid_panel/mutation_executor.rs) runs the chosen mode: SingleTransaction, ChunkedTransaction (keyset over the PK), or DirectAutocommit.
  • No-WHERE UPDATE/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 through crates/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 in dbflux_core over generic spec/metadata types — keep it there, not in dbflux_ui.
  • Persistence: migration 017_qry_saved_queries, the qry_* tables, SavedQueryRepo (crates/dbflux_storage/src/repositories/qry_saved_queries.rs), and the in-memory SavedQueryManager (crates/dbflux_ui_base/src/saved_query_manager.rs) wired into AppStateEntity. Cross-connection import verifies table existence through a TableProbe seam rather than reaching into driver code.

The document query builder is a separate rail for document collections, with the same rules:

  • The spec is DocumentQuerySpec in dbflux_core/src/query/document_query.rs. Drivers implement DocumentQueryCodec and return it from Connection::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 (and DocumentFeatures::AGGREGATE for Aggregate mode), never on a driver id. It lives in crates/dbflux_ui_document/src/document_builder/ and data_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) and SavedDocumentQueryManager (crates/dbflux_ui_base/src/saved_document_query_manager.rs).

Language Services

  • LanguageService trait in crates/dbflux_core/src/query/language_service.rs exposes validate, detect_dangerous, and editor_diagnostics. SqlLanguageService is the default impl for relational drivers.
  • Non-SQL dialects ship their LanguageService from the driver crate (e.g. TSqlLanguageService lives in dbflux_driver_mssql; MongoDB and Redis dangerous detection live in their own driver crates and route through classify_query_for_language(&QueryLanguage, &str)).
  • DangerousQueryKind enumerates 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 reference
  • ColumnRef::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:

  1. AI agent calls preview_mutation with operation parameters
  2. DBFlux generates SQL/query text or an execution preview using driver-owned generation/planning
  3. Preview returned with SQL, classification, affected objects, and warnings
  4. Agent reviews and decides whether to proceed
  5. 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_ddl surface 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) → AdminSafe
  • ADD COLUMN (non-nullable without default) → Admin (requires backfill)
  • DROP COLUMN, RENAME COLUMN, ALTER COLUMN → Admin
  • ADD CONSTRAINT (validation) → AdminSafe
  • ADD CONSTRAINT (FK with CASCADE DELETE) → Admin
  • DROP CONSTRAINT, DROP INDEX → Admin
  • DROP 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 TABLE ops
  • SQLite: Limited ALTER TABLE support (only ADD COLUMN, RENAME); DROP COLUMN requires 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() returns WindowKind::Floating on every platform. On Linux, gpui parents a floating window to the window holding keyboard focus when it opens (xdg_toplevel.set_parent on Wayland, WM_TRANSIENT_FOR on X11), so tiling compositors float it at its requested size. Do not use WindowKind::Dialog for secondary windows: it is modal and blocks input to the parent.
  • apply_window_options() sets min size so X11 WMs emit WM_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 compiled CARGO_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 hardcode dbflux / dbflux-nightly identifiers. Nightly returns a distinct app_id and DB file so it coexists with stable; Rc shares 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 via set_nightly_shares_stable_db / nightly_shares_stable_db.
  • Brand assets live under resources/branding/{stable,nightly}/ (served per channel in crates/dbflux_ui/src/assets.rs). Packaging and Nix files substitute channel placeholders. The release/nightly flow is documented in docs/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/... from docs/, ../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 in web/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       # prettier

Where the documentation is served

DOCS_MODE decides, and it has three values:

ValueWhat it buildsDocumentation URL
embedded (default)everything, one origin/docs/usage/
sitelanding pages onlyhttps://docs.dbflux.dev/usage/
docsthe 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=docs

Override 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

  1. Forgetting cx.notify() after state changes
  2. Blocking UI thread — use background_executor().spawn() for DB ops
  3. Entity updates in render loops — guard with .take()
  4. Missing feature gates on driver code
  5. Creating closures per cell in tables — use row-level handlers with hit-testing instead
  6. Canvas re-rendering every frame — cache scroll state and only sync on meaningful changes
  7. Audit event validation — EventCategory::Config requires summary, object_type, AND object_id; missing any causes silent failure in validate_event()

For key files and the cross-crate map, see ARCHITECTURE.md.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.