Skip to main content

Architecture Decisions

State Management: Zustand over Redux

FactorDecision
Stores4 small, focused stores vs. one large Redux store
PersistenceEach store uses different storage: IndexedDB (pipeline), localStorage (workflow), sessionStorage (access mgmt) — Zustand middleware is simpler
SizeEach store < 500 lines, no complex reducer logic
Server stateReact Query handles all server state, Zustand only manages local UI state

Visual Designers: XYFlow (@xyflow/react)

FactorDecision
UsagePipeline designer + Workflow designer (shared DesignerCanvas)
FeaturesCanvas with zoom/pan, node/edge rendering, connection handling, drag-and-drop
Custom nodesOperatorNode, TesterNode, 11 workflow node types
Layout@dagrejs/dagre for auto-layout (DAG algorithms)

UI Primitives: React Aria Components

FactorDecision
Design system@dtx/ui built on Untitled UI, which uses React Aria
AccessibilityARIA roles, keyboard navigation, focus management out of the box
ComponentsSelect, ComboBox, Modal, Dialog, Button, Table, Tabs, DatePicker
Tailwindtailwindcss-react-aria-components for state variant integration

Monorepo: pnpm Workspaces

FactorDecision
Packagesapps/portal, packages/ui, packages/utils
VersioningCatalog in pnpm-workspace.yaml centralizes all dependency versions
Linking@dtx/ui is source-linked (no build step, direct TypeScript imports)
Isolationpnpm strict dependency resolution prevents phantom dependencies

Other Decisions

DecisionRationale
Tailwind CSS 4 (native Vite plugin)No PostCSS config needed, faster builds, @theme block in CSS
Keycloak for authEnterprise OAuth2/OIDC with multi-tenancy, realm-based isolation
Monaco EditorFull IDE-like SQL/expression editing in pipeline operators
Recharts over D3Simpler API for standard charts; D3 available for custom visualizations
React Hook Form + ZodType-safe validation, multi-step wizard support via useFormContext()
Sonner for toastsLightweight, Tailwind integration
Manual Vite chunksSeparate chunks for react, query, monaco, d3, react-flow — optimized loading
IndexedDB for pipelinesPipeline state (nodes, edges, configs) exceeds localStorage 5MB limit
Singleton servicesAll API services use getInstance() — single instance per service
snake_case → camelCaseBackend uses snake_case; parseApiResponse() auto-converts all responses