Implementation Architecture
This document explains the implementation shape of the
irodori-table repository. It is
intentionally practical: use it to decide which directory owns a change, how
frontend code reaches Rust, and where shared logic should live.
Goals
- Keep the desktop app as the product integration point.
- Keep Rust/TypeScript command payloads generated from Rust definitions.
- Keep shared crates small and earned by stable boundaries.
- Keep database execution, streaming, cancellation, and result paging in Rust.
- Keep UI workflow, layout, and interaction state in the desktop frontend.
- Keep extension contracts separate from app-only implementation details.
System View
flowchart LR
user["User"]
desktop["Desktop app<br/>apps/desktop"]
frontend["React + TypeScript UI<br/>apps/desktop/src"]
tauri["Tauri command bridge<br/>apps/desktop/src-tauri/src"]
db["DB runtime<br/>src-tauri/src/db"]
adapters["Engine adapters<br/>postgres mysql sqlite mssql duck etc."]
crates["Shared Rust crates<br/>../irodori-kit/irodori-*"]
sdk["Extension SDK<br/>../irodori-kit/packages/extension-sdk"]
samples["Sample DBs<br/>../irodori-samples/*"]
external["External foundations<br/>irodori-sql typeship irodori-knowledge"]
user --> desktop
desktop --> frontend
frontend -->|"generated irodori-api.ts"| tauri
tauri --> db
db --> adapters
db --> crates
adapters --> samples
crates --> external
tauri --> sdk
Repository Ownership
| Path | Owns |
|---|---|
apps/desktop/src/ | React UI, workbench layout, commands, settings, editor/result interactions, client stores. |
apps/desktop/src-tauri/src/ | Tauri command handlers, database sessions, Git, terminal, local AI, security state, background jobs. |
../irodori-kit/irodori-connection/ | Portable connection profile model. |
../irodori-kit/irodori-security/ | Security/audit-facing shared model. |
../irodori-kit/irodori-core/ | Shared core model and composition around connection/security/job foundations. |
../irodori-kit/irodori-completion/ | Metadata-driven completion and inspection logic. |
../irodori-kit/irodori-connector-abi/ | Native connector ABI helpers and export macro. |
../irodori-kit/irodori-generate/ | Local SQL generation planning, schema projection, runtime, verification. |
../irodori-kit/irodori-extension/ | Rust definitions for extension API generation. |
../irodori-kit/irodori-io/ | Import/export encoders and tabular data helpers. |
../irodori-kit/irodori-proxy/ | Direct/SSH/proxy transport planning and forwarding. |
../irodori-kit/irodori-secure-store/ | OS secret store integration boundary. |
../irodori-kit/irodori-server/ | Optional local HTTP API/headless surface. |
../irodori-kit/packages/extension-sdk/ | TypeScript SDK and templates for extension authors. |
tools/ | Code generation, docs checks, extension validation, security checks. |
../irodori-samples/ | Sibling checkout with database fixtures and compose files for manual and integration testing. |
Frontend Shape
The frontend is feature-first. Cross-feature composition happens in
apps/desktop/src/app/AppWorkbench.tsx, while feature behavior should stay in
apps/desktop/src/features/*.
flowchart TB
app["AppWorkbench<br/>composition and command wiring"]
shell["WorkbenchShell<br/>chrome, menu, status bar"]
stores["Zustand stores<br/>workbench/results/preferences/connections"]
editor["query-editor<br/>CodeMirror, selection, params, magics"]
results["results<br/>grid, WebGL, chart, BI, row detail, export"]
connections["connections<br/>profiles, import/export, validation"]
sidebars["workbench views<br/>Sidebar, Inspector, Git, history, completion"]
dialogs["dialogs<br/>settings, ERD, migration, import, AI"]
api["generated API<br/>src/generated/irodori-api.ts"]
app --> shell
app --> stores
app --> editor
app --> results
app --> connections
app --> sidebars
app --> dialogs
editor --> api
results --> api
connections --> api
dialogs --> api
Frontend rules of thumb:
- Put user workflow code under the owning
features/<area>/directory. - Keep reusable pure SQL helpers under
src/sql/. - Keep global keyboard metadata in
src/core/keybindings.ts. - Keep command orchestration in
features/workbench/command-handlers.ts. - Keep large repeated UI surfaces under feature
components/. - Keep cross-feature UI primitives under
src/components/— currentlyDialogShell(shared modal chrome) andErrorBoundary. - All modals render through
DialogShell: it owns the scrim overlay, ESC-to-close, click-outside, focus trap, focus restoration, androle="dialog"/aria-modal. Do not hand-roll modal overlays. - Wrap fallible subtrees in
ErrorBoundaryso a render error degrades locally instead of white-screening the whole app (the root is wrapped inApp.tsx). - Pull cohesive workbench command/state orchestration out of the
AppWorkbenchshell into kebab-case controller hooks underapps/desktop/src/app/controllers/, such asuse-editor-commands.ts,use-result-export.ts, anduse-workspace-actions.ts. - Keep
apps/desktop/src/app/hooks/for UI-local extracted hooks whose naming already follows the older camelCase pattern, such as result-grid scroll, filtering, and selection helpers. New cross-feature orchestration should usecontrollers/; new UI-local hooks should stay near the UI surface or inhooks/when they are shared insidesrc/app. - Split multi-tab dialogs into one component per tab (e.g.
features/settings/tabs/); the dialog file stays a thin shell. - Use the design tokens in
styles/base.cssfor spacing, radius, and elevation (--space-*,--radius-*,--elevation-*) instead of ad-hoc px, and theme color tokens instead of hardcoded colors. - Add or change Tauri payloads in Rust first, then regenerate TypeScript.
Backend Shape
Tauri owns local privileged work. The frontend should not know driver details, secret storage details, or streaming/paging internals.
flowchart TB
lib["src-tauri/src/lib.rs<br/>Tauri builder and command registration"]
state["Managed state<br/>DbState JobState SecurityState AiState PtyState"]
dbmod["db module<br/>commands, connection, query, stream, spill, meta, edit"]
engine["engine adapters<br/>postgres mysql sqlite mssql duck snowflake ..."]
git["git module<br/>status, log, diff, branch, push/pull"]
pty["pty module<br/>integrated terminal sessions"]
jobs["jobs module<br/>desktop job commands"]
ai["ai module<br/>local generation state"]
security["security module<br/>audit and redaction state"]
crates["shared crates<br/>completion proxy io generate server"]
lib --> state
lib --> dbmod
lib --> git
lib --> pty
lib --> jobs
lib --> ai
lib --> security
dbmod --> engine
dbmod --> crates
ai --> crates
jobs --> crates
Backend rules of thumb:
- Add a Tauri command only when the UI needs a privileged/local boundary.
- Keep per-engine driver logic in
src-tauri/src/db/<engine>.rs. - Keep engine-independent query splitting, result shaping, streaming, and spill
behavior in
db/query.rs,db/stream.rs, anddb/spill.rs. - Keep metadata conversion in
db/meta.rs. - Keep connection profile normalization and redaction in
db/profile.rs. - Keep generated command DTOs serializable with
serde(rename_all = "camelCase")andts-rswhere they cross to TypeScript.
Command And Type Boundary
Rust is the source of truth for desktop command payloads. TypeScript consumes generated bindings rather than hand-writing copies.
flowchart LR
rust["Rust structs/enums<br/>serde + ts-rs"]
tests["cargo test export_typescript_bindings"]
generated["apps/desktop/src/generated/irodori-api.ts"]
ui["React features"]
extRust["../irodori-kit/irodori-extension"]
extGenerated["../irodori-kit/packages/extension-sdk/src/generated"]
sdk["../irodori-kit/packages/extension-sdk"]
check["typegen drift check<br/>npm run typegen:check"]
rust --> tests --> generated --> ui
extRust --> tests --> extGenerated --> sdk
generated --> check
extGenerated --> check
Use these commands:
make desktop-typegen
make desktop-typegen-check
make desktop-build-verified
Query Execution Flow
The query path is intentionally Rust-heavy so large result sets, cancellation, and disk offload stay bounded.
sequenceDiagram
participant Editor as QueryEditorPane
participant App as AppWorkbench
participant API as generated irodori-api.ts
participant Tauri as Tauri command
participant DB as DbState / db module
participant Conn as Connection trait
participant Grid as ResultsPane
Editor->>App: run current / selection / all
App->>API: dbRunQueryStream or dbRunQuerySpill
API->>Tauri: invoke command
Tauri->>DB: validate profile, query, limits
DB->>Conn: execute through engine adapter
Conn-->>DB: rows, metadata, elapsed time
DB-->>API: stream events or spill handle
API-->>App: result sets or windowed result
App->>Grid: build result view model
Grid->>API: dbResultWindow when spilled pages are needed
Important implementation details:
db_run_query_streamstreams batches for fast first paint.db_run_query_spillwrites huge results to a bounded Rust-side result store.db_result_windowpages spilled results back into the grid.db_cancelcancels active work through cancellation tokens.- The UI result grid handles sorting/filtering/editing for resident results.
- Spilled results are browse-first; server-side sort/filter is a separate workflow.
Shared Crate Layers
flowchart TB
app["apps/desktop/src-tauri"]
server["irodori-server"]
extension["irodori-extension"]
core["irodori-core"]
connection["irodori-connection"]
security["irodori-security"]
proxy["irodori-proxy"]
secureStore["irodori-secure-store"]
completion["irodori-completion"]
generate["irodori-generate"]
io["irodori-io"]
external["external git/crates<br/>irodori-sql, irodori-error, irodori-jobs"]
app --> core
app --> completion
app --> generate
app --> io
app --> proxy
app --> secureStore
app --> external
server --> core
server --> external
extension --> external
core --> connection
core --> security
core --> external
proxy --> core
secureStore --> core
completion --> external
generate --> external
io --> core
io --> external
Do not add a crate just because a future feature sounds independent. Start inside the owning app or crate, then split when there is a stable API, separate test cadence, or a real second consumer.
Extension Surface
Extensions are intentionally not the same as app internals.
flowchart LR
manifest["irodori.extension.json"]
sdk["TypeScript SDK<br/>../irodori-kit/packages/extension-sdk"]
generated["generated extension API"]
host["desktop extension host/store"]
app["workbench commands/results/themes"]
manifest --> sdk
generated --> sdk
sdk --> host
host --> app
Extension rules:
- Public extension payloads belong in
../irodori-kit/irodori-extension. - SDK convenience wrappers belong in
../irodori-kit/packages/extension-sdk/src. - App-only plugin registry and loading state belongs under
apps/desktop/src/features/extensions. - Templates must stay permissively licensed and validate through
make extension-manifests.
Parallel Agent Development
Parallel work is organized around explicit workstreams rather than informal file
ownership. The detailed policy lives in
parallel-agent-architecture.md, and the
machine-readable source is
registry/agent-workstreams.json.
The key split is:
- One coordinator agent owns registry and generated-catalog source files:
knowledge/engines.json,registry/catalog/*.json, andapps/desktop/src-tauri/src/db/engine.rs. - Connector agents are repeatable and own exactly one generated sibling
repository under
../irodori-extensions/irodori-extension-*. - Extension-host, DB-runtime, migration/diff, and workbench-UI agents own separate source trees and serialize changes only when shared contracts move.
- Generated outputs are changed by the owner of the generator input, not by downstream agents.
Validate the workstream map with:
node tools/docs/agent-workstreams.mjs
Adding A Feature
- Decide the owner.
- UI workflow:
apps/desktop/src/features/<area>. - Local privileged action:
apps/desktop/src-tauri/src/<area>. - Stable shared model: an existing crate, not a new crate by default.
- UI workflow:
- If TypeScript calls Rust, define or update the Rust DTO/command first.
- Regenerate bindings with
make desktop-typegen. - Wire the UI through
generated/irodori-api.ts, not handwritten invoke calls. - Add focused unit tests near the owning feature.
- Run the smallest relevant checks, then broaden if the boundary changed.
Typical checks:
make desktop-typegen-check
npm --prefix apps/desktop test
npm --prefix apps/desktop run build
cargo test --workspace
Current Refactor Pressure Points
These areas work but should be watched because they are high-change/high-size:
apps/desktop/src/app/AppWorkbench.tsx: integration point. Move feature logic into hooks, stores, or feature components when touching it.apps/desktop/src/features/connections/connection-transfer.ts: importer coverage is broad. Keep parsers test-backed.apps/desktop/src/sql/completion.ts: completion logic should continue moving towardirodori-completionwhen it becomes engine-independent.apps/desktop/src/theme/index.ts: theme normalization should stay data-driven.apps/desktop/src/features/results/components/WebGlResultGrid.tsx: rendering behavior should stay isolated from result model construction.
Architecture Guardrails
apps/desktopis allowed to orchestrate product UX.crates/*should not know about React, Tauri windows, or app layout.- Rust command payloads should use camelCase at the JSON/TS boundary.
- Long-running work should expose progress/cancel/logs through jobs.
- Huge data should stream or spill; do not buffer full result sets in the UI.
- Secret values should not be logged, copied into generated bindings, or stored in plain frontend state.
- Reference-project research belongs in docs or requirements, not copied code.