Architecture
FORGE Studio is an Electron 33+ desktop application with a React 18 + Redux renderer. This page covers the key architectural decisions and how the major subsystems connect.
Process Model
┌──────────────────────────────────────────────────────────────┐
│ Renderer Process (React + Redux) │
│ window.forgeAPI.* ◄──── contextBridge (preload/index.ts) │
└──────────────────────────────┬───────────────────────────────┘
│ IPC (forge:* channels)
┌──────────────────────────────▼───────────────────────────────┐
│ Main Process (Node.js) │
│ ipc-handlers.ts → job-manager.ts → backend-dispatcher.ts │
│ ↓ │
│ FORGE/Python/Julia/MATLAB child processes │
└──────────────────────────────────────────────────────────────┘- The renderer communicates with the main process exclusively through
window.forgeAPI(defined inpreload/index.ts). Direct Node.js access is disabled (nodeIntegration: false,contextIsolation: true). - Push events (job progress, JSONL, file changes, toasts) flow main → renderer via
webContents.send. - Redux manages all UI state.
ipc-middleware.tsbridges push events into Redux actions.
Key Files
Main Process
| File | Purpose |
|---|---|
src/main/index.ts | Entry point: creates BrowserWindow, registers IPC handlers, starts hardware detection |
src/main/ipc-handlers.ts | All ipcMain.handle and ipcMain.on registrations |
src/main/job-manager.ts | N-stage pipeline orchestration, job history, crash recovery |
src/main/backend-dispatcher.ts | Routes stages to FORGE/Python/Julia/MATLAB runners |
src/main/interpreter-session.ts | Persistent interpreter sessions with FORGE_RESP: protocol |
src/main/config-writer.ts | Writes per-step JSON config files |
src/main/forge-cli.ts | Builds FORGE binary CLI commands from workflow params |
src/main/jsonl-parser.ts | Parses FORGE stderr JSONL event stream |
src/main/workflow-loader.ts | YAML parsing + JSON Schema validation |
src/main/palette-loader.ts | Palette card YAML loading/saving |
src/main/compute-backend.ts | GPU/runtime detection, backend selection |
src/main/stage-cache.ts | SHA-256 hash-based stage output caching |
src/main/batch-manager.ts | Batch queue management, scan, retry |
src/main/file-watcher.ts | chokidar NIfTI output watcher |
src/main/twix-reader.ts | Siemens TWIX ASCCONV header reader |
src/main/ismrmrd-reader.ts | ISMRMRD HDF5 header reader |
src/main/yarra-converter.ts | Yarra .mode import/export |
src/main/settings-store.ts | electron-store typed wrapper |
Preload
| File | Purpose |
|---|---|
src/preload/index.ts | contextBridge.exposeInMainWorld('forgeAPI', …) |
Renderer
| File | Purpose |
|---|---|
src/renderer/App.tsx | Root: lazy tab routing, ToastStack, FirstRunWizard |
src/renderer/store/ | Redux store + 9 slices |
src/renderer/store/ipc-middleware.ts | Wires push events into Redux dispatches |
src/renderer/tabs/ | SetupTab, MonitorTab, ViewerTab, HistoryTab, WorkflowsTab, SettingsTab, MaskEditorTab |
src/renderer/components/ | Shared UI components |
src/renderer/theme/ | Design token system (38 tokens) + ThemeProvider |
Shared
| File | Purpose |
|---|---|
src/shared/ipc-types.ts | ForgeAPI interface + all IPC payload types — single source of truth |
src/shared/workflow-types.ts | WorkflowFile, WorkflowStage, WorkflowStep |
src/shared/param-types.ts | WorkflowParam discriminated union (7 types) |
src/shared/palette-types.ts | PaletteCard, PaletteLibrary |
IPC Contract
All renderer → main calls go through window.forgeAPI methods. The contract is defined in src/shared/ipc-types.ts → ForgeAPI interface.
All channels use the forge: prefix internally. Never bypass forgeAPI — treat the renderer as untrusted.
Push events (main → renderer) are subscribed via onXxx(callback) methods that return an UnsubscribeFn. They are wired in ipc-middleware.ts and dispatch into Redux slices.
Pipeline Execution
startJob(config)
└── job-manager: executeJob(job)
├── for each stage:
│ ├── check cache → if hit: advance currentInputPaths, continue
│ ├── dispatchStage({ inputPaths: currentInputPaths, ... })
│ │ ├── backend: forge → runForgeStage (spawn binary)
│ │ ├── backend: python → runInterpreterStage (persistent session)
│ │ ├── backend: julia → runInterpreterStage
│ │ └── backend: matlab → runInterpreterStage
│ ├── currentInputPaths = outputPaths ← stage chaining
│ └── store cache entry
└── emit forge:job:state (completed/failed/cancelled)- Only one active job at a time (
JOB_ALREADY_RUNNINGerror otherwise). - Each stage runs in sequence, passing its outputs to the next stage as inputs.
- Interactive stages pause and wait for
approveGate/rejectGateIPC calls.
Persistent Interpreter Sessions
Script backends (Python, Julia, MATLAB) keep a single interpreter process alive for the entire job duration. This eliminates cold-start latency on subsequent stages.
Protocol:
- Spawn interpreter with
forge_dispatcherscript. - Wait for
FORGE_RESP:{"type":"ready"}on stdout. - Send
{"type":"run","module_path":"...","config_path":"..."}on stdin. - Read streamed
FORGE_RESP:events (log, progress, metrics, image_preview). - Wait for
FORGE_RESP:{"type":"ok","output_paths":[...]}or{"type":"error"}.
Julia supports pre-compiled sysimages (--sysimage) to cut cold-start from seconds to milliseconds.
Stage Caching
Each stage is identified by a SHA-256 hash of:
- Stage definition (YAML subtree)
- Input file path
- Workflow ID + schema version
- Stage index
If the hash matches a stored cache entry and the cached files still exist, the stage is skipped. Cache entries live at ~/.forge-studio/cache/<16-char-hash>/manifest.json.
Caching can be disabled globally in Settings → Pipeline → Enable Stage Caching.
Redux State Shape
{
job: JobState, // active job, stage results, session state
monitor: MonitorState, // JSONL events, log lines, resource snapshots
viewer: ViewerState, // loaded volumes, overlays, NiiVue state
workflows: WorkflowsState, // workflow list cache
editor: EditorState, // workflow editor state (visual + YAML)
history: HistoryState, // past jobs
settings: SettingsState, // app settings, hardware info
ui: UIState, // active tab, toasts, dialogs, theme
batch: BatchState, // batch queue, active batch summary
updater: UpdaterState, // auto-updater status
}File Watcher
When a job starts, file-watcher.ts watches each stage's output directory using chokidar. When a .nii or .nii.gz file appears, a debounced forge:file:changed event is sent to the renderer, which auto-switches to the Viewer tab.
Design System
The UI uses a 38-token design system defined in src/renderer/theme/tokens.ts. Both dark and light themes are defined. Never hardcode colors — always use useTheme() tokens.
Base components: Btn, Input, Select, Card, Badge.