Skip to content
Internal Symulab · project reference v0.1.43

Symulab — what we've built so far.

This page is the working reference for the team and a small group of testers. It describes the desktop app in its current state — the features that are wired up, the sample models included, the engine internals, and the known rough edges. The macOS build is in the sidebar; the release log at the bottom records what changed.

§ 01 What's in the build

The four pieces of the app, as of v0.1.43.

A snapshot of the current build. Each block names the actual UI components and engine pieces involved — useful both for testers (where to look) and for the team (where to fix things). Status notes and known issues are in the knowledge base below.

  1. 01 Canvas

    Diagram authoring

    React Flow canvas with three node types — Stock, Flow, Converter — plus influence-link edges. Properties panel for inline editing of names, initial values, and rate expressions. Multi-dimensional (arrayed) variables via the dimension manager.

    Canvas · PropertiesPanel · DimensionManager

  2. 02 JSON · STMX
    {json}

    Model files & import

    Models are saved as a single JSON file containing the diagram, equations, and simulation settings.

    Toolbar (open/save) · run_model.jl

  3. 03 Julia engine

    Simulation

    The Tauri Rust shell spawns Julia with the precompiled sysimage; the engine reads the model, builds the ODE system, solves with Tsit5 (OrdinaryDiffEq), and writes results back. Cold start ~2s after the sysimage work.

    SmsEngine.jl · sysimage

  4. 04 3-tab panel
    Chart Table Equations

    Results & export

    Three tabs on the results panel — Chart (Recharts time-series), Table (raw values by step), and Equations (generated system). CSV export of any series. Debug console for diagnosing run failures.

    ResultsChart · ResultsTable · EquationsPanel · DebugConsole

Bundled sample models logistic_growth.json predator_prey.json predator_prey_stella.json civilisation_model.json world_dynamics.json

Notes on the current build.

Grouped reference: what's working in this version, how to test it, what the engine does at runtime, and the known rough edges we already know about. Skim by topic; expand the answers you want.

A. Current state

What's working today

  • What can I actually do in this build?

    Open or create a model, draw stocks/flows/converters on the canvas, edit names/initial values/rate expressions in the properties panel, run the simulation, and see results in the three-tab results panel (Chart, Table, Equations).Models save to a single .json file you can reopen anywhere.

  • Are dimensions / arrayed variables wired up?

    Yes — partially. You can define dimensions in the DimensionManager and apply them to nodes; the ArrayBadge on each node shows the active dimensions. The engine expands arrayed variables into independent state at simulation time. Some authoring shortcuts (per-element overrides, subscript notation in expressions) are still rough.

  • What about graphical functions (lookup tables)?

    Native authoring UI (drawing the curve in the converter properties panel) is the next thing on this track.

  • Is CSV export working?

    Phase 1: yes. You can export any series from the Table tab as CSV. Multi-series export and unit headers are pending.

B. For testers

Testing the build

  • How do I install it?

    Grab the build for your OS from the downloads block at the top of this page (macOS .dmg, Windows .exe). Julia is bundled inside the installer — you don't need a separate Julia install. First launch on macOS may need a right-click → Open to bypass Gatekeeper since the build isn't notarized yet.

  • What should I try first?

    Open logistic_growth.json from the sample models. Hit Run. Confirm the S-curve renders in the Chart tab. Then change K (the carrying capacity converter) from 1000 to 500, hit Run again, and check that the curve plateaus earlier. That covers the full loop: load → edit → simulate → plot.

  • How do I report something that's broken?

    Open the Debug Console (View menu) — it captures the Julia stderr/stdout and any validation errors from the front-end. Copy the relevant output and send it along with a short description of what you did. If the app crashed entirely, the Tauri log on macOS is at ~/Library/Logs/Symody/. Working on integrating Sentry for better reporting of all issues.

C. For the team

Engine internals

  • How does a Run actually execute?

    Front-end serializes the in-memory model to JSON and calls the run_simulation Tauri command. The Rust shell writes the payload to the long-lived Julia worker's stdin (no per-run process spawn). run_model.jl parses the model, builds the ODEProblem (one state per stock, one parameter per converter), solves with Tsit5, and writes the result JSON back on stdout. Rust reads the line, returns it to the front-end, and the results panel renders it. Stderr is streamed live as Tauri events for the in-app debug console.

  • Why a persistent Julia worker instead of one-shot scripts per Run?

    Cold-starting Julia per Run wastes ~2s on JIT and package load every time. The Rust shell instead spawns one worker on app startup with --threads=auto, --project=<julia_dir>, and --sysimage=<bundled .dylib>, then pipes ops over stdin and reads results from stdout as line-delimited JSON. Subsequent Runs cost only the solve. The supervisor auto-respawns if the worker dies, and a 120s per-call timeout protects the UI from a stuck simulation.

  • What does the Rust shell actually do?

    Three jobs. (1) Window + native APIs — open/save dialogs, menus, file dialogs via tauri 2. (2) Worker supervision — find the bundled julia binary in the resource dir (fallback: dev path, then system julia), launch with the sysimage flag, manage stdin/stdout/stderr pipes through tokio, respawn on death. (3) IPC — expose read_file, write_file, and run_simulation as Tauri commands; emit julia-status and simulation-log events so the UI can render lifecycle and progress.

  • What's in the Julia module (SmsEngine.jl)?

    Model parsing (julia/src/SmsEngine.jl with run_model.jl as the worker entry point), expression-to-callable compilation, ODE assembly (flows summed into stock derivatives), and result serialization. Dependencies are pinned in julia/Project.toml: OrdinaryDiffEq, JSON3, PackageCompiler, CairoMakie, Base64. The Manifest is gitignored — each build resolves fresh.

  • Why precompile a sysimage?

    Cold start without it is ~15 seconds because of Julia's JIT. The sysimage (julia/build_sysimage.jl + precompile_workload.jl) bakes the loaded packages and a representative simulation workload into a .dylib/.so/.dll that gets bundled with the app. Result: first simulation per session runs in ~2 seconds. The sysimage is built in CI and shipped inside the installer.

  • Why CairoMakie if Recharts handles the in-app plots?

    Two reasons. First, Recharts gives us interactive in-app rendering but doesn't help when we want a publication-quality PDF or PNG export from a headless run. Second, the engine generates the equation diagrams for the Equations tab as static images — that path goes through CairoMakie.

  • Why Tauri instead of Electron?

    Smaller installer (Electron would add 100–200 MB of Chromium), lower idle memory, memory-safe Rust shell, and the OS's native webview is plenty for a React app. The tradeoff is occasional webview-specific quirks (mainly on Windows WebView2) — manageable.

D. Status

Known issues & roadmap

  • Known issues in v0.1.0

    No multi-run overlay (re-run replaces the previous chart). No per-element overrides for arrayed variables in the properties panel — you have to use array literals. Graphical-function authoring UI is missing (engine support exists). Windows installer is unsigned. macOS build is signed but not notarized.

  • Near-term roadmap

    UI Icons updates, multi-run overlay on the chart, built-in SD function library (SMOOTH/TREND/DELAY first), per-element overrides for arrayed variables, parameter sweep view, code signing for Windows, macOS notarization.

E. Designs in flight

Planned capabilities

  • Sentiment analysis module — what is it?

    A new engine capability that takes a batch of texts (survey responses, support tickets, social mentions, qualitative interview transcripts) and returns per-document polarity and confidence scores, plus an aggregate distribution. Designed to feed straight into a Stock or Converter as a time-bucketed signal — so a system-dynamics model can have its loops driven by how a population actually feels, not just by abstract proxies.

  • Which Julia libraries will it use?

    Planned: TextAnalysis.jl for tokenization, stemming, and the classic lexicon/bag-of-words pipeline; WordTokenizers.jl for robust tokenization including punctuation/emoji edge cases; Languages.jl for language detection so non-target-language inputs are skipped or routed; Transformers.jl with a small distilled BERT (e.g. distilbert-base-multilingual) for the high-quality pass; MLJ.jl as the wrapper around the lexical baseline classifier. All baked into the sysimage so cold-start stays flat.

  • How will it use multi-CPU?

    The worker already runs with --threads=auto, so all CPU cores are visible to Julia. Batch scoring is embarrassingly parallel across documents — we'll partition the batch with ChunkSplitters.jl and dispatch chunks via Threads.@threads, keeping one tokenizer/model handle per thread to avoid lock contention. For the transformer pass, Transformers.jl with the CPU backend amortizes well across threads on M-series and modern x86. Single-document latency is unchanged; throughput on a 10k-doc batch scales near-linearly with cores.

  • Two-pass design — why?

    Lexicon-based scoring is fast (millions of tokens/sec) but blunt on negation, sarcasm, and domain language. Transformer scoring is accurate but ~100× slower per document. The plan is to run the lexicon pass first to triage — confidently positive/negative items keep their score; only the ambiguous middle band (e.g. |score| < 0.4) goes to the transformer for a second look. Median throughput stays high while edge cases get the quality treatment.

  • How does it plug into the app?

    No second process. The existing Julia worker gains a new submodule (SmsEngine.Sentiment) and a new IPC op: the Rust shell writes {"op":"sentiment","texts":[...],"options":{...}} to stdin, the worker scores, and writes back {"scores":[...], "aggregate":{...}} on stdout. A new Tauri command (run_sentiment) exposes it to the front-end. From the UI side, it surfaces as a Converter source: pick a column of text from an imported CSV, choose the bucket size (daily/weekly), and the sentiment series becomes available like any other variable.

§ 03 Architecture

How the pieces fit together at runtime.

A short reference for the team — what each layer owns, which libraries do the work, and how a Run actually moves from a click on the canvas to a plotted result.

fig. 02 — runtime topology
INTERFACE React webview · React Flow SHELL Tauri / Rust tokio · process supervisor ENGINE Julia worker persistent · --threads=auto tauri command stdin · op stdout · result STDERR → tauri events (logs) sysimage .dylib · cold start ~2s
fig. 03 How a Run flows
  1. 01 Serialize

    Front-end snapshots the in-memory model (nodes, edges, sim settings) to JSON.

  2. 02 Invoke

    A Tauri command crosses the webview→host bridge into the Rust shell.

  3. 03 Dispatch

    Rust writes the payload to the long-lived Julia worker's stdin — no per-run process spawn.

  4. 04 Solve

    Engine parses the model, builds the ODE system, runs Tsit5 from OrdinaryDiffEq.jl.

  5. 05 Stream back

    Results JSON returns on stdout; logs/errors stream on stderr as Tauri events.

fig. 04 Runtime libraries

Interface

What the user sees

  • React 18 view tree, hooks
  • React Flow diagram canvas
  • Recharts time-series plots
  • Tailwind styling

Shell

Native host (Rust)

  • Tauri 2 window, menus, dialogs
  • tokio async process + stdio pipes
  • serde command (de)serialization
  • Worker supervisor lifecycle + auto-respawn

Engine

Persistent Julia worker

  • OrdinaryDiffEq.jl ODE solver (Tsit5)
  • JSON3.jl fast JSON over stdio
  • CairoMakie.jl static plot / equation diagrams
  • PackageCompiler.jl sysimage build for cold-start
  • Base64 image payload encoding

Note — Everything runs on the user's machine. There is no cloud step in the simulation path; no model data leaves the laptop.

§ 04 Releases

Every build, written down.

Each release ships with a short note: what changed, what's new, what to watch out for. Builds are pushed to Cloudflare R2 from CI on every tag.

  1. v0.1.22 release 19 Jul 2026

    Symulab comes to Windows. Plus an engine correctness pass and a smoother canvas.

    The first Windows build ships — a 396 MB installer with the full Julia engine and precompiled sysimage inside. Under it, a correctness audit of the equation pipeline fixed seven real bugs (two of which silently produced wrong numbers), backed by a new analytic verification suite and full pipeline documentation. And the canvas got a feel overhaul: groups carry their contents, valves travel with their stocks, marquee multi-select, double-click rename, and drags that stay smooth on big models.

    • Windows installer (x64) — bundled Julia runtime + precompiled sysimage, no separate install; built and published from CI
    • Stella import fix: 1-arg RAMP and 1/2-arg PULSE translated with the time argument in the wrong slot — silently constant or zero. Omitted arguments are now padded correctly
    • Stella import: LN, LOGN, INT, and uppercase math functions now fold to their engine equivalents instead of failing at run time
    • Multi-dimension arrays fixed — pop[North,Old] subscripts now compile (element keys previously produced unparseable identifiers)
    • A flow whose dimensions don't match its stock is now a clear error in both the editor and the engine — previously it silently moved nothing
    • Stock init expressions are validated with did-you-mean suggestions instead of surfacing raw engine errors
    • Groups carry their contents when dragged; stocks carry their source/sink valves; pipes render straight with an arrowhead into the receiving stock
    • Marquee multi-select (Shift+drag), Cmd+G groups the whole selection, double-click renames, zoom out to 10%
    • Dragging is committed on drop instead of every mouse move — large models stay smooth, and one drag is one undo step
    • New engine verification suite checks results against closed-form analytic solutions across all three integrators; docs/equations.md documents the full equation pipeline

    The headline: Symulab now runs on Windows. The installer carries the complete Julia compute engine — runtime, packages, and a precompiled system image — so simulation works offline out of the box, exactly like the Mac build. No Julia install, no downloads on first launch. Behind the scenes this took a full rework of the build pipeline; the practical result is a 396 MB installer produced automatically from CI for every release.

    The equations had an audit. Because a modeling tool is only as good as its numbers, we traced every step from the expression you type to the value in the chart — and fixed seven real bugs. The two that mattered most produced silently wrong results: Stella-imported RAMP(slope) and short-form PULSE(...) calls put the simulation time into the wrong argument slot (a 1-arg RAMP was permanently zero), and a scalar flow connected to an arrayed stock moved nothing at all, with no warning. Both now behave correctly — and the mismatch case is a clear error in the editor and the engine, so it can never fail quietly again. The rest of the pass: LN/INT/uppercase math functions from Stella files now translate instead of erroring, the constant e works in expressions, two-dimensional array subscripts like pop[North,Old] compile correctly, SUM/MIN of a non-arrayed variable behave as identity instead of erroring, and init expressions are validated up front with did-you-mean suggestions.

    Trust, but verify. The engine now ships with a verification suite that runs the real worker process against models with known closed-form solutions — exponential decay and logistic growth across Euler, RK4 and Tsit5, integrator convergence-order checks, converter dependency chains, array aggregation, bounds clamping, and every helper function. The full pipeline — how groups, dimensions, expressions, validation, and integration actually work — is documented in docs/equations.md.

    The canvas feels right now. A drag-and-drop overhaul fixed the things you’d notice within a minute of modeling: dragging a group now carries its members with it; dragging a stock carries the source and sink valves wired to it; the flow-to-stock magnetic snap no longer fights the alignment grid, so butt-joints stay flush; and pipes render as straight, rigid lines with an arrowhead into the receiving stock, so flow direction is always readable. Interaction got richer too — Shift+drag draws a selection marquee, Cmd+G groups everything selected, double-clicking a node jumps straight to renaming it, and the canvas zooms out to 10% for big models. Under the hood, node positions are now committed once on drop rather than on every mouse move, which keeps large diagrams smooth and makes one drag equal one undo step. Smaller touches: a live size readout while resizing, connection handles that appear on hover instead of dotting every node, and consistent array badges across node types.

    Known follow-ups queued for the next releases: a direct Windows download link on this site (the build currently ships via GitHub Releases), Windows code signing to quiet SmartScreen, Stella SMOOTH/DELAY import support, and graphical (lookup-table) functions.

  2. v0.1.20 patch 29 Jun 2026

    Resizable nodes, friendlier labels, real Max bounds, first-order feedback.

    Canvas polish from a QA pass. Nodes are now resizable (Stock free, Flow aspect-locked, Converter 1:1). Names render with spaces instead of underscores. Stock and Converter Max values are honored by the engine. Animation toggle for all connectors. Dragging a stock back to a flow it's already piped to now draws the influence arrow for first-order feedback. Plus several quieter fixes.

    • Resize Stock, Flow, and Converter nodes via corner/edge handles (selection-only); Converter stays a circle, Flow stays 3:2, Stock is free (Shift to lock)
    • Display labels strip underscores — 'birth_rate_max' shows as 'birth rate max' on the canvas; equations and exports keep the identifier form
    • Stock and Converter Max (and Min) now clamp during simulation — previously only flow rates were bounded
    • Flow valve redesigned: bigger central regulator that matches converter circle weight, shorter pipe stubs, recognizable bowtie kept
    • Animate connectors toggle in Display → Connector Style — applies to pipes and influences uniformly, defaults ON
    • First-order feedback: dragging a stock to a flow it's already piped to now creates an influence arrow instead of being a silent no-op
    • Fresh canvas no longer shows a red 'Model is empty' error — toolbar reads OK until you actually run an incomplete model
    • Pressing Run on an empty model returns empty results instead of throwing
    • Snap math respects resized node dimensions
    • Engine packaging fix — SmsEngine now declares the stdlib deps the disaggregation tool needs (SparseArrays, LinearAlgebra)

    A polish release driven by QA feedback. Most of the work is on the authoring surface — sizes, names, animations, and a couple of edge cases that made the canvas feel rougher than it had to.

    Resizable nodes. Every node type now has the standard React Flow resize handles (visible only when selected). Stocks resize freely; hold Shift while dragging to lock the current aspect ratio. Flows stay locked to a 3:2 ratio so the valve geometry holds its shape. Converters stay 1:1 — the circle is the circle, not an ellipse. Defaults are 120 × 80 for Stock and Flow, 80 × 80 for Converter. Sizes persist in the model JSON, so a laid-out diagram round-trips cleanly.

    Friendlier labels. Names with underscores (the engine’s required identifier form) now render on the canvas with spaces — birth_rate_max becomes birth rate max, Hares_Hunted_Per_Lynx becomes Hares Hunted Per Lynx. The underscored form is preserved everywhere that needs it: in equations, in CSV exports, in the Dashboard, in chart legends and tooltips. The transform is canvas-only, so identifiers stay greppable and matchable to the Julia engine’s symbol names.

    Real Max bounds. Stock and Converter Max (and Min) values previously sat in the UI but were silently dropped by the engine — only flow rates were clamped. They now flow through end-to-end: initial values are clamped at start, the integrator gates derivatives at the bound during the run, computed converter values are clamped each step, and the recorded series are clamped at the end so any solver overshoot doesn’t leak into exports. A logistic model with population.max = 400 now clamps cleanly at 400 instead of climbing to the natural carrying capacity.

    Flow valve redesign. The flow’s center regulator was previously a 12-pixel knob lost between the bowtie shoulders. It now reads at converter scale — a 44-pixel central circle, with the bowtie kept as flanking accents and the pipes shortened to 20-pixel stubs that exit the sides. The valve still looks like a Stella valve; it just doesn’t disappear next to a converter anymore.

    Animate connectors toggle. Pipes (stock ↔ flow) previously sat still while influence edges marched — visually inconsistent. The animation now applies to every connector type, and a new toggle in Display → Connector Style lets you turn it off entirely. Defaults to on for new models; existing models with no display.animate default to on too.

    First-order feedback wiring. Drawing a Population → Deaths influence arrow on top of an existing pipe used to be a silent no-op — the connection logic treated the second drag as a redundant pipe edit. The connect handler now detects the case (source stock is already this flow’s from or to) and creates the influence edge instead, so the diagram can actually show the feedback loop. Doesn’t fire for unrelated stocks.

    Quieter fixes.

    • Fresh canvas (zero nodes) no longer flags a red error in the toolbar; the validation badge stays OK until something is actually wrong.
    • Clicking Run on an empty model returns empty results instead of throwing — matches the new validation badge behavior.
    • The flow-to-stock magnetic snap now reads each node’s actual width and height, so resized stocks and flows snap correctly to their real edges, not a default-sized phantom box.
    • The Julia engine package (SmsEngine) now declares SparseArrays and LinearAlgebra as direct dependencies — the disaggregation tool used them but the package wasn’t listing them, which broke a clean precompile when the sysimage was rebuilt from scratch.

    Known follow-ups queued from the QA review and not in this release: unit-of-measure support with consistency checking, auto-inference of input/output variables from rate expressions, and movable waypoints on the connector lines. Those each need a brainstorm before they ship.

  3. v0.1.16 patch 22 Jun 2026

    Disaggregation tool (WIP), Sentry monitoring, leaner CI.

    First cut of the temporal-disaggregation utility (Denton PFD, work in progress), Sentry error and performance monitoring wired into the renderer, and a Windows-only release pipeline. Plus the usual round of fixes.

    • New Tools → Disaggregate Data… utility — CSV in, disaggregated series out (WIP)
    • Sentry monitoring: errors, performance traces, and session replay
    • Build & Release workflow trimmed to Windows-only
    • Smaller stability and quality fixes across the canvas and simulation engine

    A maintenance release with one new tool, observability, and a CI cleanup.

    Temporal disaggregation (work in progress). A new entry under Tools → Disaggregate Data… opens a standalone utility for converting low-frequency series (e.g. annual totals) into a higher-frequency series (quarterly, monthly, …) using the Denton Proportional First Differences method, with an optional indicator column for benchmarking-style runs. Load a CSV, pick the columns and the split factor, and the result comes back as a chart, a table, and a downloadable CSV. The engine and the core flow are in place; rough edges, edge cases, and broader method support are still being worked through — treat this build as a preview.

    Sentry monitoring. The renderer now reports uncaught errors, performance traces, and (sampled) session replays to Sentry. Anything that crashes a panel or trips an unexpected code path should now leave a trail we can actually look at.

    Build & Release pipeline. The GitHub Actions workflow that produces shippable artifacts has been reduced to Windows-only. Mac and Linux lanes have been removed; the workflow runs faster and the only artifact it publishes is the Windows installer.

    Other fixes. A handful of smaller fixes landed across the canvas, validation, and simulation engine — the kind of polish that doesn’t fit a bullet but keeps the desktop experience steady.

  4. v0.1.8 preview 26 May 2026

    Canvas polish — snap, continuous pipes, configurable labels.

    Flows magnetise to stocks and auto-wire on drop. Pipes between stocks and flows read as one continuous line — handles, color seams, and the redundant arrow at the stock boundary are gone. Variable name and value labels are positionable per model (default: name above, value below). The toolbar gains a recent-files dropdown and a Reset-all in the palette.

    • Flows snap to stock edges within 30 px and auto-wire from/to
    • Continuous stock↔flow pipes — no handle dots, no color seams, no boundary arrow
    • 10 % smaller source/sink cloud glyphs
    • Configurable name & value label positions (top / bottom / inside / hidden)
    • New defaults: variable name above the node, value below
    • Per-edge connector style via right-click — mix curved, angled, straight on the same diagram
    • Recent-files dropdown next to Open — eight most-recent, auto-pruned
    • Palette: per-slot reset links and a Reset-all that clears palette + per-object overrides
    • Marketing site served from a Cloudflare Worker (stocksflow-web)
    • macOS downloads now served via CloudFront, stable URL

    This is a polish release focused on the authoring surface. Three themes:

    Canvas behaviour. Dragging a flow within ~30 px of a stock’s left or right edge now magnetises the flow against the stock, aligns it vertically, and sets flow.from or flow.to automatically based on which side the stock is on. Only the closer side wires, so moving one end of an already-connected flow never accidentally drops the other. Snap thresholds live in src/lib/snap.ts if you want to tune the catch zone.

    Visual continuity. With flows sitting flush against stocks so much more often, anything breaking the visual continuity at the join had to go. The pipe-handle dots on flows are now transparent, the synthetic pipe-edges pick up the flow’s own color so there’s no seam at the boundary, the right-side arrowhead vanishes when the right end is wired into a stock, and the flow’s wrapper is locked to its SVG width so long rate-expression labels can no longer push the SVG sideways. Source / sink clouds shrank by 10 % to match.

    Labels. Each diagram now picks where the variable name and the value / expression label sit — top, bottom, inside the body, or hidden — independently for the two label kinds. New models default to name above and value below, which matches Stella / Vensim conventions and keeps the stock and converter glyphs uncluttered. Labels are absolutely positioned so they never affect the node’s bounding box — snap math stays exact no matter how long the name is. The chosen positions persist as a user preference and apply to every fresh model.

    Quality-of-life in the toolbar. A small chevron beside Open drops down the eight most-recent models — pulled from the preferences store, pruned when a file is missing. The palette popover now reports how many overrides are active (per-slot plus per-object) and lets you wipe all of them with a single confirm.

    Distribution. The marketing site now lives on a Cloudflare Worker named stocksflow-web, deployed via wrangler login and the new scripts/deploy-web.sh. The macOS download button on this page points at the CloudFront distribution in front of the symody-artifacts S3 bucket — stable URL, no signing, no expiry.

  5. v0.1.0 preview 12 May 2026

    First testable build — everything the app does today.

    End-to-end loop is working: draw a stock-flow diagram, save it as JSON, run the simulation in the bundled Julia engine, and read results across three views (chart, table, equations). macOS, Windows, and Linux installers — Julia runtime included.

    • Stock / Flow / Converter node types on a React Flow canvas
    • Properties panel — names, initial values, rate expressions
    • Dimensions / arrayed variables (DimensionManager + ArrayBadge)
    • Save and reopen models as a single .json file
    • Stella .stmx import (predator-prey verified against Stella's output)
    • Julia ODE engine — OrdinaryDiffEq with Tsit5, ~2s cold start
    • Three-tab results panel — Chart, Table, Equations
    • CSV export of any series (phase 1)
    • Debug Console for inspecting Julia stderr/stdout
    • Bundled Julia runtime + precompiled sysimage in installer
    • Cross-platform builds via GitHub Actions → Cloudflare R2

    This is the version of the app currently in testers’ hands. The list above is everything wired up end-to-end. The notes below give context.

    Authoring. The canvas takes the three SD primitives plus influence links. Names, initial values, and rate expressions edit inline through the properties panel. The DimensionManager lets you define named dimensions (e.g. Region = {N, S, E, W}) and apply them to any node — the ArrayBadge on the node shows what’s active and the engine expands to the right number of state variables at run time.

    Persistence. One .json file per model. Diagram, equations, and simulation settings all in one place. Diffable in git. The five bundled sample models in /models exercise different parts of the engine — logistic_growth.json is the smoke test, predator_prey_stella.json plus Predator-Prey_(Stella)_results.csv is the faithfulness check against Stella, civilisation_model.json and world_dynamics.json are the heavier stress tests.

    Engine. Tauri spawns the bundled julia with our sysimage flag, hands off paths to a temp input and a temp output file, and reads the result back. The Julia side (SmsEngine.jl + run_model.jl) parses the model, builds the ODEProblem, solves with Tsit5 from OrdinaryDiffEq, and serializes the trajectory back as JSON. The sysimage gets first-run latency down from ~15s to ~2s.

    Results. Three tabs in the results panel. Chart is the Recharts time-series with a checkbox per series. Table is the raw step-by-step values, with a per-column CSV export. Equations shows the system of equations the engine assembled — useful for sanity-checking that what you drew is what the engine actually solved.

    Distribution. GitHub Actions builds the matrix (macOS / Windows / Linux), packages the installer with the bundled Julia runtime and sysimage, and uploads to Cloudflare R2 under releases/{version}/. The download links on this page point at that bucket. macOS build is signed; Windows is not yet; notarization is pending.