Skip to content
Open
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ See [docs/changelog-template.md](docs/changelog-template.md) for formatting your
- [#208](https://github.com/2listic/dealiiX-platform/pull/208) You can now give a single run or a pipeline run a custom name before submitting (remote mode); it's used to name the run's output folder instead of a bare timestamp, making it easy to find on disk or in the visualizer. Leaving the name blank keeps today's timestamp-based naming. If the same name is submitted twice, the second run gets a unique suffix instead of overwriting the first. The Jobs table also gains a "Working Directory" column so you can see where each job's files landed.
- [#207](https://github.com/2listic/dealiiX-platform/issues/207) The Pipeline editor gains "Export pipeline" and "Import pipeline" buttons: export downloads the current pipeline (all stages, connections, and per-stage settings) as a JSON file; import loads one back in, replacing the canvas.

### UI/UX

- [#204](https://github.com/2listic/dealiiX-platform/issues/204) Execution mode is now chosen from an always-visible selector in the top-right corner instead of the Settings modal: one dropdown picks the location (local/remote) and another picks the mode (coral, executable, or pipeline). Switching is instant and no longer re-probes the backend, and the app reopens in the last selected mode. Pipeline mode is available only in remote (and disables the local option), matching that pipelines run remotely. Each location now keeps its own validated node registry and parameters, so switching between local and remote — or between coral and executable — never runs one target's configuration against another's; importing nodes merges into the current location's registry rather than replacing it. In Settings, the "Execution paths" section keeps location/backend-kind radios, but they now only choose which combination to configure and **Validate & Sync** (renamed from "Save & Sync Execution") — this no longer changes the active mode, so you can set up and validate any of the four combinations without leaving your current view. A combination that hasn't been validated yet prompts you to open Settings and its runs are blocked. The separate Run controls are unified into a single sidebar Run button that runs the graph in single mode and the pipeline in pipeline mode; the pipeline editor's own toolbar (add stage, import/export, run) moved into the sidebar.

## [1.4.1] - 2026-07-14

### Canvas-graph
Expand Down
15 changes: 8 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,20 @@ The application uses two JSON protocols to communicate with CORAL:
Stores use Svelte 5 runes (`.svelte.ts` files):

- `nodes.svelte.ts` - Central store for flow nodes/edges. Exports `getNodes()`, `getNodesSnapshot()`, `setNodes()`, `getEdges()`, `getEdgesSnapshot()`, `setEdges()`, `addNode()`, `addEdge()`, `removeNode()`, `updateLastNodeId()`.
- `registryStore.svelte.ts` - Registry for available node types (both standard and network). Exports `setRegistry()`, `getNodeData(type)`, `getAvailableNodes()`, `isNodeInRegistry(type)` for standard nodes; and `addNetworkNode(key, data)`, `removeNetworkNode(name)`, `getNetworkNodeDefinition(name)`, `getStoredNetworkNodes()`, `isNodeInNetworkNodes(name)` for subgraph nodes. Both registries are persisted in electron-store and loaded at startup.
- `registryStore.svelte.ts` - Registry for available node types (both standard and network). Standard nodes are kept **per execution location** (`local`/`remote`). **Reads** resolve an internal `activeLocation` (set via `setActiveLocation(location)`; App syncs it from `executionSelectionState.location` via an effect — no `settingsState` import); **writes take an explicit location** so they never move the read cursor: `setRegistry(data, location)` (replace that location's registry), `mergeRegistry(data, location)` (merge into it — used by manual "Import Nodes"). Read APIs `getNodeData(type)`, `getAvailableNodes()`, `isNodeInRegistry(type)` resolve `activeLocation`. Subgraph nodes are location-agnostic: `addNetworkNode(key, data)`, `removeNetworkNode(name)`, `getNetworkNodeDefinition(name)`, `getStoredNetworkNodes()`, `isNodeInNetworkNodes(name)`. Persisted in electron-store (`registered_nodes` as `{ local, remote }`, with best-effort migration from the old flat shape; `registered_network_nodes`) and loaded at startup.
- `graphStack.svelte.ts` - Navigation context stack for entering/exiting subnetworks. `graphStackState` has `breadcrumbs`, `canGoBack`, `currentLabel` reactive getters, plus `pushContext()`, `collapseToParent()`, `updateTopContext()`, `updateParentContext()`, `reset()` mutators, and `syncCurrent()` which snapshots the live canvas into the top stack entry before any navigation action. `graphHistoryState` exposes the per-level undo/redo API: `canUndo`/`canRedo` reactive getters; `checkpoint()` (single-step actions), `begin()`/`commit()` (multi-step gestures), `undo()`, `redo()`, `clearHistory()`. Each `GraphContext` holds `current: HistoryEntry` (navigation snapshot), `past: HistoryEntry[]`, and `future: HistoryEntry[]`.
- `graphNavigation.svelte.ts` - High-level subnetwork navigation actions built on top of `graphStack`. Exports `enterSubnetwork(nodeId)` (drills into a subnetwork node), `loadParentGraph()` (navigates back, persisting edits), and `renameCurrentSubnetwork(name)` (renames without navigating away).
- `nodeIdCounter.svelte.ts` - Isolated store for generating unique node IDs (`getNextNodeId()`). Kept separate from `nodes.svelte.ts` to avoid importing electron side-effects in utility/test contexts.
- `colorModeStore.svelte.ts` - Light/dark theme state. `colorModeState.value` (get/set) and `colorModeState.toggle()`. Setting automatically syncs to electron IPC (`set-theme`) and persists to electron-store under `colorMode`.
- `parametersStore.svelte.ts` - Transient store for the CORAL parameter tree (loaded from a run). `parametersState.value` (get/set) and `parametersState.snapshot` (non-reactive copy).
- `parametersStore.svelte.ts` - Store for the executable/CORAL parameter tree, kept **per execution location** and persisted (`execution_parameters`). `parametersState.value` (get/set) and `parametersState.snapshot` resolve an internal `activeLocation` (set via `setActiveLocation(location)`); `setValueFor(location, value)` writes a specific location's tree without moving the cursor (used when validating a non-active target from Settings).
- `executionSelection.svelte.ts` - The active **execution selection** — which `location` (`local`/`remote`) and `backendKind` (`coral`/`executable`) the UI points at. Pure renderer selection state (Electron never reads it), kept separate from the persisted runner config in `settingsStore`; persisted under `execution_selection` so the app reopens in the same mode. `executionSelectionState` exposes getters `location`, `backendKind`, `isCoralMode`, `isExecutableMode` and async setters `setLocation(location)`, `setBackendKind(kind)`. `ExecutionBadge` writes it directly (the mode dropdown maps to `viewModeState` + `setBackendKind`); `App.svelte` syncs the registry/params `activeLocation` from it via an effect.
- `auth.svelte.ts` - JWT token for coral-remote-server API. Methods: `setToken()`, `setUsername()`, `clearToken()`. Persisted in electron-store under `access_token` / `username`.
- `settingsStore.svelte.ts` - User settings stored under a single `'settings'` key in electron-store. Exports named key constants (`SSH_PATH`, `URL_VISUALIZER`, `URL_REMOTE_SERVER`, `USE_MPI`) and a `settingsState` object with `getKey(key)` / `setKey(key, value)` methods.
- `settingsStore.svelte.ts` - **Pure config store** — user settings persisted under a single `'settings'` key in electron-store (`AppSettings`: `execution` is just `{ local, remote }` runner-config targets — paths + each target's per-backend-kind `probes` status; `location`/`backendKind` live in `executionSelection`, not here). It never imports the selection store; every selection-dependent member takes `location`/`backendKind` as **explicit arguments**. `settingsState` exposes getters `execution`, `remote`, `local`, `getParametersFileName(location)`, `getProbe(location, kind)`, and async setters `saveExecutionPaths(execution)` (persist edited paths, preserving probe status), `recordProbe(location, kind, status)`, `saveParametersFileName(location, name)`, `saveUrlVisualizer`, `saveUrlRemoteServer`. In `settingsActions.ts`, `probeAndSaveExecution(location, backendKind, execution)` probes (via a focused `ProbeRequest`), routes the returned payload to the per-location registry/params store (`applySyncedMetadata`), and records the status; `warnIfUnvalidated(location, kind)` toasts when a combination has no successful probe.
- `currentProjectStore.svelte.ts` - Current project metadata (`id`, `name`). Methods: `set()`, `clear()`, `updateName()`. Exports `ApiProject` interface for use in components.
- `jobsStore.svelte.ts` - Slurm job tracking. `jobsState` has `isEmpty`/`oneOrLess` getters, `update()` refreshes from SSH. `jobIdMapState` maps Slurm scheduler IDs → a `JobIdEntry` (`internalId` used in touch-dir paths, `backendKind`, and the per-job `workingDirectory`). The `workingDirectory` is the root for single runs and the per-stage subdirectory for pipeline stages; `getOutFileContent` / `getNodesExecutionStatus` resolve a job's `slurm-<id>.out` and `nodes-exec-status/` from it (via `getJobWorkingDirectory` / `getEntryByInternal`), so logs and node status work uniformly for both.
- `toastsStore.svelte.ts` - Toast notifications. Use `toastState.add({ message, type })` to show a toast. Supports `'error'`/`'success'` types with auto-dismissal.
- `dndStore.svelte.ts` - Drag-and-drop state (`dndNodeDataState.current`) for tracking which sidebar node is being dragged onto the canvas.
- `viewModeStore.svelte.ts` - Which central editor is shown: `viewModeState.value` is `'graph' | 'pipeline'`, with `toggle()`. Transient (not persisted). `App.svelte` renders `PipelineCanvas` when `'pipeline'`, else the graph/parameters editor.
- `viewModeStore.svelte.ts` - Which central editor is shown: `viewModeState.value` is `'single' | 'pipeline'`, with `toggle()`. Transient (not persisted; resets to `'single'` each launch). `App.svelte` renders `PipelineCanvas` when `'pipeline'`, else the graph/parameters editor. The overlay mode dropdown composes this with `executionSelectionState.backendKind` into `coral | executable | pipeline`.
- `pipeline.svelte.ts` - Transient store for the pipeline editor, holding the pipeline as xyflow `Node[]`/`Edge[]` (one node per stage, one edge per ordering dependency). Mirrors the get/set shape of `nodes.svelte.ts` (`getNodes`/`setNodes`/`getEdges`/`setEdges`) for canvas binding, plus `pipelineState` with `addCoralStage()`, `addExecutableStage()`, `updateStageData()`, `updateStageConfig()`, `setStageParameters()`, `removeStage()`, `clear()`, `toPipeline()`, and a `validation` getter.

### Pipeline Orchestration
Expand Down Expand Up @@ -328,7 +329,7 @@ cd /app && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build
- **Svelte 5 runes**: Uses `$state`, `$derived`, `$effect` for reactivity (not legacy stores). Use `$state.snapshot()` when passing reactive state to non-reactive contexts. See Code Conventions for the writable `$derived` pattern.
- **Parameter file formats** (`src/lib/utils/parameterFileFormat.ts`): Shared utility (imported by both the Svelte frontend and the Electron main process) for parsing and serialising parameter files in JSON or deal.II `.prm` format. Key exports: `parseParametersFileWithFormat(content, fileName?)` (auto-detects format via content sniffing), `serializeParametersFile(tree, fileName?)` (format derived from the filename extension), `getParameterProbeFileNames(fileName)` (returns JSON candidate first, then PRM). **JSON is always preferred over PRM** when probing an executable — JSON carries rich metadata (validation patterns, default values, documentation) while PRM stores every value as plain text. Format detection uses content sniffing first, filename extension second.
- **IPC channels**: `execute-ssh-with-key`, `export-graph-ssh`, `set-theme`, `open-external-url`, `upload-file-ssh`, `store:get`, `store:set`, `store:remove`
- **Electron storage keys** (`electron/utils/storage.ts`): `access_token`, `username`, `settings`, `colorMode` (default: `'light'`), `registered_nodes`, `registered_network_nodes`, `jobs`, `jobIdMap`
- **Electron storage keys** (`electron/utils/storage.ts`): `access_token`, `username`, `settings`, `colorMode` (default: `'light'`), `registered_nodes` (per-location `{ local, remote }`), `registered_network_nodes`, `execution_parameters` (per-location params), `execution_selection` (active `location`/`backendKind`), `jobs`, `jobIdMap`
- **Electron TypeScript build**: `electron/**/*.ts` files are compiled by `tsc` (not Vite) to `dist-electron/`. The main process uses `tsconfig.electron.json` (ESM output); the preload uses `tsconfig.electron.preload.json` (CJS output, required by Electron's sandboxed preload context). `dist-electron/` is gitignored. Type declarations for `ssh2` (which ships no `.d.ts`) live in `electron/types/ssh2.d.ts`.
- **Git hooks (Husky)**: On commit — `npm run lint` and `npm run check:electron` (failures abort), then `lint-staged` runs Prettier on staged files only and re-stages the formatted result, so formatting changes land in the same commit.
- **CI (GitHub Actions)**: All workflows run `npm run check` (svelte-check), `npm run check:electron` (electron tsc), and `npm test`. Workflow files: `.github/workflows/ci.yml`, `.github/workflows/release-linux.yml`, `.github/workflows/release-macos.yml`.
Expand All @@ -337,9 +338,9 @@ cd /app && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build
- **Auto-layout**: `applyAutoLayout(nodes, edges, direction?)` in `src/lib/utils/autoLayout.ts` repositions nodes using the dagre rank-based algorithm (`'LR'` by default). Call after loading or structurally modifying a graph when node positions aren't provided.
- **Registry validation**: `filterValidNodes(registry)` in `src/lib/utils/registryValidator.ts` validates a raw registry payload from CORAL, returning a `[validRegistry, skippedKeys]` tuple. Used when loading the registry at startup to skip malformed entries.
- **Key type enums** (`src/lib/types/`): `ConnectionType` (INPUT/OUTPUT/PASSTHROUGH), `ExecNodeStatus` (FAILED/SUCCEEDED/RUNNING), `JobStatus` (COMPLETED/FAILED/PENDING/RUNNING), `nodeColors` (maps node types to CSS colors), `HIDDEN_SIDEBAR_NODE_TYPES` (excludes ABSTRACT and NETWORK from sidebar listing).
- **Slurm batch templates**: Two templates in `src/lib/templates/` — `sbatch.template.sh` (non-MPI) and `sbatch-mpi.template.sh` (MPI via `mpirun --allow-run-as-root -np ${SLURM_NTASKS:-1}`). Imported at build time via Vite's `?raw` suffix. `sshMessages.ts` selects between them based on the `USE_MPI` setting. Both templates expose `{{INTERNAL_JOB_ID}}` (used for `--touch-dir nodes-exec-status/<id>`) and `{{TIME_LIMIT}}`. The MPI template additionally exposes `{{NODES}}` and `{{NTASKS_PER_NODE}}`, filled at runtime from `JobConfig` (defaults: 1 node, 4 tasks/node, 01:00:00 time limit). Clicking Execute always opens `JobConfigModal.svelte` (renamed from `MpiConfigModal.svelte`) — it shows MPI-specific fields (nodes, tasks/node) only when MPI is enabled, and always shows the time limit field, plus an optional run-name field (remote mode) used to name the run's output folder. `PipelineCanvas.svelte`'s "Run pipeline" button opens the analogous `PipelineRunNameModal.svelte` for the same purpose before calling `runPipelineRemote`.
- **Slurm batch templates**: Two templates in `src/lib/templates/` — `sbatch.template.sh` (non-MPI) and `sbatch-mpi.template.sh` (MPI via `mpirun --allow-run-as-root -np ${SLURM_NTASKS:-1}`). Imported at build time via Vite's `?raw` suffix. `sshMessages.ts` selects between them based on the `USE_MPI` setting. Both templates expose `{{INTERNAL_JOB_ID}}` (used for `--touch-dir nodes-exec-status/<id>`) and `{{TIME_LIMIT}}`. The MPI template additionally exposes `{{NODES}}` and `{{NTASKS_PER_NODE}}`, filled at runtime from `JobConfig` (defaults: 1 node, 4 tasks/node, 01:00:00 time limit). The single sidebar Run button (`SidebarButtons.svelte`) is context-aware: in single mode it opens `JobConfigModal.svelte` (renamed from `MpiConfigModal.svelte`) — which shows MPI-specific fields (nodes, tasks/node) only when MPI is enabled, always shows the time limit field, plus an optional run-name field (remote mode) used to name the run's output folder; in pipeline mode it opens `PipelineRunNameModal.svelte` and calls `runPipelineRemote`. The pipeline stage/import-export controls also live in `SidebarButtons` (shown in pipeline mode), so `PipelineCanvas.svelte` is a pure canvas. Execution location/mode are chosen from the interactive `ExecutionBadge.svelte` dropdowns in the top-right overlay (`App.svelte`), not from Settings.
- **MPI graph payload**: When MPI is enabled, `buildGraphPayload()` in `sshMessages.ts` injects a `plugin: { MPI: { enabled: true, max_num_threads: 1 } }` block at the top of the exported network JSON, as required by CORAL for MPI initialization.
- **Node execution status**: `getNodesExecutionStatus(jobIdInternal)` reads files from `/app/shared-data/nodes-exec-status/<internalJobId>/` on the remote server, returning a `Map<qualifiedNodeId, string[]>` of status sequences (e.g. `'running'`, `'succeeded'`, `'failed'`).
- **Node execution status**: `getNodesExecutionStatus(location, jobIdInternal)` reads files from `/app/shared-data/nodes-exec-status/<internalJobId>/` on the remote server (or the local run dir), returning a `Map<qualifiedNodeId, string[]>` of status sequences (e.g. `'running'`, `'succeeded'`, `'failed'`). Like `getOutFileContent(location, jobId)`, it takes the execution location explicitly (callers pass `executionSelectionState.location`).

## Git Workflow

Expand Down
6 changes: 3 additions & 3 deletions electron/ipcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { dialog } from 'electron'
import fs from 'fs'
import type {
AppSettings,
ExecutionSettings,
ProbeRequest,
} from '../src/lib/types/settingsTypes.js'

import {
Expand Down Expand Up @@ -58,8 +58,8 @@ export function registerIpcHandlers(): void {

ipcMain.handle(
'probe-sync-execution-settings',
async (_event, executionSettings: ExecutionSettings) => {
return await probeAndSyncExecutionSettings(executionSettings)
async (_event, request: ProbeRequest) => {
return await probeAndSyncExecutionSettings(request)
}
)

Expand Down
Loading