Skip to content

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 in preload/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.ts bridges push events into Redux actions.

Key Files

Main Process

FilePurpose
src/main/index.tsEntry point: creates BrowserWindow, registers IPC handlers, starts hardware detection
src/main/ipc-handlers.tsAll ipcMain.handle and ipcMain.on registrations
src/main/job-manager.tsN-stage pipeline orchestration, job history, crash recovery
src/main/backend-dispatcher.tsRoutes stages to FORGE/Python/Julia/MATLAB runners
src/main/interpreter-session.tsPersistent interpreter sessions with FORGE_RESP: protocol
src/main/config-writer.tsWrites per-step JSON config files
src/main/forge-cli.tsBuilds FORGE binary CLI commands from workflow params
src/main/jsonl-parser.tsParses FORGE stderr JSONL event stream
src/main/workflow-loader.tsYAML parsing + JSON Schema validation
src/main/palette-loader.tsPalette card YAML loading/saving
src/main/compute-backend.tsGPU/runtime detection, backend selection
src/main/stage-cache.tsSHA-256 hash-based stage output caching
src/main/batch-manager.tsBatch queue management, scan, retry
src/main/file-watcher.tschokidar NIfTI output watcher
src/main/twix-reader.tsSiemens TWIX ASCCONV header reader
src/main/ismrmrd-reader.tsISMRMRD HDF5 header reader
src/main/yarra-converter.tsYarra .mode import/export
src/main/settings-store.tselectron-store typed wrapper

Preload

FilePurpose
src/preload/index.tscontextBridge.exposeInMainWorld('forgeAPI', …)

Renderer

FilePurpose
src/renderer/App.tsxRoot: lazy tab routing, ToastStack, FirstRunWizard
src/renderer/store/Redux store + 9 slices
src/renderer/store/ipc-middleware.tsWires 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

FilePurpose
src/shared/ipc-types.tsForgeAPI interface + all IPC payload types — single source of truth
src/shared/workflow-types.tsWorkflowFile, WorkflowStage, WorkflowStep
src/shared/param-types.tsWorkflowParam discriminated union (7 types)
src/shared/palette-types.tsPaletteCard, PaletteLibrary

IPC Contract

All renderer → main calls go through window.forgeAPI methods. The contract is defined in src/shared/ipc-types.tsForgeAPI 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_RUNNING error otherwise).
  • Each stage runs in sequence, passing its outputs to the next stage as inputs.
  • Interactive stages pause and wait for approveGate / rejectGate IPC 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:

  1. Spawn interpreter with forge_dispatcher script.
  2. Wait for FORGE_RESP:{"type":"ready"} on stdout.
  3. Send {"type":"run","module_path":"...","config_path":"..."} on stdin.
  4. Read streamed FORGE_RESP: events (log, progress, metrics, image_preview).
  5. 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

typescript
{
  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.

FORGE Studio