Unlinked is a Rust web application for opening Simulink diagrams, sharing versioned models within organizations, and running a growing subset of their simulations. It also includes a MATLAB/Octave-to-Rust compiler with LLVM IR output through rustc.
The application follows single-binary-rust-website: Yew compiled to WebAssembly, Axum serving embedded frontend assets, shared Rust protocol types, PostgreSQL with Diesel migrations, and a single deployable backend binary. See PLAN.md for the architecture and remaining milestones, and the original template conventions for background. That archived template describes the starting point; the code and instructions below describe Unlinked.
- Import ZIP/XML
.slxand legacy textual.mdlmodels, retaining unknown blocks and parameters. Render root diagrams and nested subsystems as SVG; inspect blocks in the browser. - Edit models in the browser with Simulink's gestures: quick insert, ports that snap, lines that follow and branch, smart guides, in-place renaming, alignment and find. Saves are new file versions that keep everything the editor does not model. Run a simulation from the diagram and see its recorded values on the lines.
- Organization and project permissions, Google/GitHub OAuth, immutable file versions, and audit events. Viewers can read models and run simulations; editors can change files.
- Simulation jobs pinned to an immutable file version, with authenticated HTTP results and a typed WebSocket stream. Euler, RK4, and adaptive Dormand–Prince RK45 solvers are available.
- A retained Diagram/Simulate workspace with guarded navigation, searchable signals, sample cursors and interval measurements, reference-run overlays, CSV export, and recorded run settings that can be explicitly reused. Project folders have shareable URLs, filtering, and pagination.
- Simulation of the supported block subset, including basic sources, arithmetic, integrators, unit delays, switching, logic, and bounded transfer functions. Unsupported blocks and settings produce explicit errors.
- MATLAB/Octave compilation to standalone Rust or LLVM IR. See compiler documentation for the implemented language subset.
This is an independent implementation with partial compatibility. Rendering a model does not mean it can be simulated. Graphical Stateflow execution, arbitrary toolbox/library behavior, MATLAB callbacks, and general masked or conditional subsystem execution are not implemented. Stateflow diagrams and MATLAB Function code render; pure scalar MATLAB Function blocks execute when their metadata and backing structure match the supported subset. Model callbacks are not executed. Supply parameter values explicitly or select an authorized, versioned project initialization script; that path uses a bounded interpreter with no file or process access. Solver settings must be selected explicitly when running from the CLI.
The separately licensed test corpus records upstream URLs, pinned revisions, licenses, checksums, and expected results where available. Corpus tests cover import and rendering; analytic and Octave differential tests cover numerical behavior. Passing import/render checks is not a numerical equivalence claim. See compatibility evidence and configured execution scenarios for the current inventory.
Install Rust, PostgreSQL development headers (libpq-dev on Debian/Ubuntu), and the WASM bundler:
rustup target add wasm32-unknown-unknown
cargo install trunk --locked
cp .env.example .env
docker compose up -d db
(cd frontend && trunk build)
cargo run -p backend -- --dev-modeOpen http://localhost:3000. Development mode enables a local login and uses an ephemeral cookie key when SESSION_SECRET is unset. Sessions then end on restart. Use development mode only on a trusted local machine. The compose database port binds to localhost; its sample database password is for development.
Build the frontend before compiling the backend: memory-serve embeds frontend/dist at compile time. Rebuild both after changing browser code. Keep Trunk's hashed asset filenames enabled: HTML revalidates while hashed assets receive long-lived caching.
cargo build -p unlinked-cli
cargo run -p unlinked-cli -- coverage /path/to/unlinked-test-cases -o coverage.json
cargo run -p unlinked-cli -- info model.slx
cargo run -p unlinked-cli -- render model.slx -o diagram.svg
cargo run -p unlinked-cli -- render model.slx --system Controller -o controller.svg
cargo run -p unlinked-cli -- sim model.mdl --stop 10 --step 0.01 --solver rk45 --var 'K=2*pi' -o trace.csv
cargo run -p unlinked-cli -- transpile example.m -o example-rust
cargo run -p unlinked-cli -- transpile example.m --emit llvm-ir -o example.llRepeat --system for each nested subsystem; each argument is an exact block name, including literal slashes. SVG output uses an opaque dark background. Simulation output supports JSON and CSV; RK45 exposes --rtol, --atol, and --max-internal-steps. Run a subcommand with --help for all options.
The /transpile browser page generates downloadable typed Rust Cargo projects using ndarray and nalgebra locally, without uploading source or running the result. LLVM emission requires local Cargo and rustc. It builds the generated project without executing it; Cargo may fetch dependencies. The web server does not spawn a compiler. Selected initialization scripts run only through the bounded MATLAB interpreter; uploaded files and model callbacks never execute automatically. CLI/model browser inputs are bounded at 16 MiB; server file storage has a separately configurable upload limit.
Set DATABASE_URL, PUBLIC_URL to the exact externally visible origin, and a persistent SESSION_SECRET containing at least 64 bytes. Generate a secret with openssl rand -base64 64; keep it outside source control. Production startup fails without a valid secret and at least one configured OAuth provider.
Configure all three variables for Google and/or GitHub as shown in .env.example. Register the corresponding /api/auth/callback/google or /api/auth/callback/github URL with the provider. ALLOWED_EMAIL_DOMAINS optionally restricts sign-in. Serve production traffic over HTTPS, including behind a reverse proxy; production session cookies are secure. OAuth state, flow expiry, session revocation, same-origin mutation checks, and organization isolation have automated tests. A real provider sign-in still needs validation with your registered credentials.
Migrations are embedded and run at startup. Back up PostgreSQL: it stores model bytes, immutable history, memberships, sessions, audit records, and simulation results. Simulation concurrency caps are per backend process (four jobs globally and two per user); multi-instance global scheduling is a remaining deployment milestone.
The Dockerfile packages a prebuilt binary, following the template's CI flow:
(cd frontend && trunk build --release)
cargo build --release -p backend --locked
mkdir -p build-output
cp target/release/backend build-output/backend
docker build -t unlinked .Supply production configuration at runtime. The compose backend intentionally has no usable session-secret fallback. The bundled compose database credentials are a local example, not a production credential configuration. CI builds release artifacts and the container, and publishes main-branch images to GHCR.
Shared DTOs live in shared; database models remain backend-only. Project file uploads create immutable versions. Simulation routes are:
POST /api/files/:file_id/simulations: run a pinned version and return its persisted result.GET /api/files/:file_id/simulations: list recent runs.GET /api/simulations/:run_id: read an authorized result./ws/simulations: streamSimulationStarted, sample-majorSimulationSamples, and terminalSimulationStatusmessages. Supports cancellation and checks session validity before each new run.
See shared/src/simulation.rs for request and stream types. Run records preserve the chosen settings and workspace overrides. The optional init_script: {file_id, version} selects a .m file in the same project; submission pins its immutable version. Explicit workspace overrides take precedence over the script. Text labels and empty temporaries are omitted from the numeric workspace; references to those omitted bindings fail instead of falling back to stale model values. Script syntax, memory, statement count and expression work are bounded; IO, external functions, and arbitrary callbacks are rejected. Disconnecting a streaming client cancels its worker. A background task marks expired abandoned records failed. Each user is limited to 50 accepted runs per day; older results are pruned to retain approximately the latest 20 runs (plus any active jobs). Jobs, queues, output volume, input size, and solver work have explicit bounds.
Core crates separate model IR, import, rendering, MATLAB semantics, simulation, and CLI. Keep the computational crates WASM-compatible; native process invocation belongs in the CLI. Add migrations as YYYY-MM-DD-HHMMSS_description directories and run the naming check.
./scripts/check-migration-names.sh
cargo fmt --all --check
(cd frontend && trunk build)
cargo clippy --workspace --all-targets --locked -- -D warnings
UNLINKED_TEST_CASES=/path/to/unlinked-test-cases cargo test --workspace --locked
cargo clippy -p frontend --target wasm32-unknown-unknown --all-targets -- -D warningsSet DATABASE_URL to a disposable PostgreSQL test database to run database-backed tests; without it those tests are skipped. Install Octave for differential compiler tests. Set UNLINKED_TEST_CASES to enable the external corpus checks. CI supplies PostgreSQL and checks out the corpus; native and WASM builds are both relevant.
Apache-2.0. Imported models retain their individual licenses in the test corpus. Simulink and MATLAB are MathWorks trademarks; this project is not affiliated with MathWorks.