flowRS Open the IDE

Overview

A browser-native dataflow graph that emits Rust. You wire a diagram in the IDE; FlowRS generates a Rust program from that graph. Apache-2.0.

The signal graph emits Rust in one direction. crates/flowrs-core/src/codegen/rust.rs walks the diagram and writes the program. Nothing parses that program back into the diagram.

A separate artifact round-trips a visual syntax tree. crates/flowrs-core/src/rir/bridge.rs converts Rust source to an RIR tree, to graph nodes, and back again. That path is a syntax tree, and it is a different diagram from the signal graph.

The builtin widget census (273 builtin widgets: 0 seams, 16 stand-ins, 257 shipping) is computed from the registry, never typed: seam_widget_ids, stand_in_widget_ids and shipping_widget_count in flowrs_core::graph::widget, and the site's pages print it when they are built. A seam is codegen that emits unimplemented! at declared defaults, in process or init. The generated program compiles, then panics on the first run. A stand-in compiles and runs, but what runs is a deterministic substitute for the model or device it is named after. Models: the gen.* widgets (10) (seeded noise for Stable Diffusion, a byte histogram for CLIP, a pitch class for Whisper) and ai.complete, whose generated program answers with a local word rotation. Devices: audio.record and microphone return a synthetic 1 kHz tone, audio.play only counts samples, video.open / video.write move raw frames through flowRS's own container instead of a codec. No embedded widget (12) is a stand-in: on the Host target each drives a simulated in-memory board, and on the Embassy/RTIC targets in the Code view they reach the real pin, UART, I2C, SPI, ADC or timer through flowrs-hal (and on the Linux target through flowrs-device's LinuxBoard), with T0 and the firmware held byte for byte on the STM32F4 and RP2040 twins (crates/flowrs-pil/tests/mcuio_acceptance.rs). Each declares Widget::stand_in, carries "(stand-in)" or says so in its summary, and the editor badges it. The count lives in crates/flowrs-core/tests/seam_visibility.rs. The count covers declared defaults only. It does not mean every widget behaves the same in the interpreter and in generated Rust: a codegen-only widget does not run in the interpreter, and unexecuted lists it as codegen_only with its reason. Where each node runs is computed too (412 of the palette's 419 nodes run in the browser; 7 only in the exported program), by flowrs_core::graph::tiers from the executors themselves: the palette marks every node that runs only in the exported program, with the reason, before it is dropped, and a run in the editor lists each node it did not run, with why.

Statechart

flow.statechart is one of those nodes. The machine is a line-oriented parameter. The interpreter and the generated Rust run that chart the same way. The generated program is not parsed back into the diagram.

A parallel state raises done on itself when every region is final. One region finishing does not. assign stores KEY=VALUE on the data wire when a state is entered. A guard reads a key with KEY==VALUE, KEY!=VALUE, or an integer comparison. on: and always: may also say assign KEY VALUE. That write happens when the transition is taken, after its trace line and before entry. One key. A delay reads the elapsed wire in milliseconds and does not sleep. after: and always: cannot stay. on: SRC EVENT -> . stays in the active state and does not run exit or entry.

The node reference on the site is generated from the registry, including this node's parameter text.

Workspace

The root Cargo.toml lists the workspace's member crates (68 member crates). embedded/ and crates/embedded/ are teiOS firmware trees with their own manifests. Workspace commands skip them.

flowrs-ui is the IDE (Leptos, wasm). flowrs-server and flowrs-energy are native-only. Energy's optional nvml feature pulls libloading and has no wasm build.

Open the IDE

Prerequisites: Rust stable, the wasm32-unknown-unknown target, and Trunk. Building from crates/flowrs-ui selects that crate's nightly toolchain (rust-toolchain.toml there) because shared-memory rayon needs build-std.

make dev

That runs:

cd crates/flowrs-ui && trunk serve --address 127.0.0.1 --port 8100

Open http://127.0.0.1:8100/ide/ . crates/flowrs-ui/Trunk.toml sets public_url = "/ide/".

Serve from crates/flowrs-ui. The root index.html is an old Trunk page; Trunk at the repo root builds that page.

Desktop

crates/flowrs-desktop is a workspace member. It opens a native window on the flowrs server (cargo build -p flowrs-server) and loads /ide/. Tauri links system WebKit, so the workspace clippy, test, and wasm lines exclude flowrs-desktop. make dev is unchanged: Trunk from crates/flowrs-ui on http://127.0.0.1:8100/ide/ .

From a clean shell at the repo root:

export CARGO_TARGET_DIR=/tmp/flowrs-target-desktop
cargo build -p flowrs-server
cargo build -p flowrs-desktop
"$CARGO_TARGET_DIR/debug/flowrs-desktop"

make desktop runs crates/flowrs-desktop/open.sh, which runs the same builds (target dir /tmp/flowrs-target-desktop unless CARGO_TARGET_DIR is already set) and then opens the window. If dist/ide is missing and Trunk is already on 127.0.0.1:8100, the shell proxies the IDE and keeps /api on the flowrs server.

Test

make check   # fmt, cargo machete, native clippy, wasm clippy, flowrs-ui's wasm clippy (-D warnings)
make test    # native tests, the onnx-feature tests, the twins' node tests
make audit   # cargo audit, cargo deny, npm audit

The Makefile holds the exact lines, with the crates each one excludes and why: flowrs-ui is wasm-only and builds from its own directory, where crates/flowrs-ui/.cargo/config.toml and its nightly toolchain apply; flowrs-desktop links system WebKit; the native processes (the server, MCP, energy, the journal and the task runner) stay off the wasm line.

cd crates/flowrs-ui && cargo check --target wasm32-unknown-unknown

Browser tests are Playwright, under e2e/ (127 spec files), with an npm dependency. They need Chrome.

cd e2e && npm ci && npm test

Playwright starts trunk serve --release --address 127.0.0.1 --port 8100 --no-autoreload in crates/flowrs-ui and loads http://127.0.0.1:8100/ide/ . Headed Chrome is the default. HEADLESS=1 runs headless. make dev is the same app and port with reload, and without --release.

Kani proofs are separate: make verify runs cargo kani -p flowrs-core. It is slow, it is not part of make test, and it is not in CI: on the shared build host a model checker needs a memory budget per harness.

CI excludes

The excludes are the Makefile's (make check, make test); CI runs those targets rather than its own copies of the lines.

The workflow is .forgejo/workflows/ci.yml (make check and make test on the self-hosted macos Forgejo runner). The repository is not on the forge yet, so that workflow has not run.

Supply chain

These rules are enforced. make audit runs them, and CI runs make audit.

  • Lockfiles are authoritative. Every cargo build, test, run and install in the Makefile, scripts/, tools/ and CI passes --locked, and Trunk does too (locked = true in crates/flowrs-ui/Trunk.toml). A build that would change Cargo.lock fails. cargo kani has no --locked, so make verify and the kani scripts check the lock first.

  • make deny runs cargo deny check against deny.toml. It fails on:

    • a vulnerable, unsound or yanked crate;
    • a licence outside the allow-list (GPL, AGPL and LGPL are never linked);
    • a wildcard version;
    • a source other than crates.io;
    • a banned crate or version: the 2026 malicious crates and the floors on rustls, rustls-webpki, webpki-roots and tokio.

    An ignored advisory must name the reason it is unreachable, and the date it was checked, in both deny.toml and .cargo/audit.toml.

  • npm installs come from committed lockfiles. e2e/, tools/mcp-interop/, tools/fsrs-oracle/ and crates/flowrs-viz/oracle/ each have a package-lock.json beside their package.json. Consumers run npm ci, never npm install. An .npmrc sets ignore-scripts, because no package here needs an install script. make npm-audit audits the e2e lockfile.

  • Fetched inputs are pinned by digest:

    • CI actions by commit SHA;
    • the CI container by image digest;
    • rustup-init and other installers by SHA-256, never curl | sh;
    • vendored npm and GitHub release tarballs by a recorded integrity value (tools/vendor_js.py, tools/vendor_kokoro.py);
    • QEMU's build inputs by SHA-256 or commit (tools/qemu-wasm/host/).

License

Apache-2.0.