`widgets.spec.ts` took a one-shot `boundingBox()` of a fill that is still moving. Two ways that reads wrong. "a bar draws a row per reading" compared two widths that are both zero until the socket has delivered the readings, and a plain `expect` over a captured number does not retry. "a reading over its scale fills the track and no more" allowed the fill one pixel past the track, but the house spring is underdamped: `motion`'s own generator puts the fluksio look at 100.66% of the track 276ms in (material 100.91%, glass 103.58%), which on this tile's ~240px track is ~1.6px — over the tolerance, and reached only if the sample happens to land near the peak. Neither is the widget. `fractionOf` clamps the reading to [0, 1] and `.dui-bar-track` is `overflow: hidden`, so nothing past a full track is ever drawn; a bounding box just is not clipped by an ancestor's overflow. So both now poll: one until the shorter reading is drawn shorter, the other until the fill is flush with its track — which is a stricter reading of "and no more" than the pixel of slack was. The live-socket test gains the other half of what that spin cost: an ASGI task that never returns is one uvicorn's graceful shutdown waits on forever, which is why `fluksio serve` could only be SIGKILLed and left `serve.pid` behind.
Fluksio
Fluksio is a node-based automation software that brings trust and reliability to your flow. It just works and looks good. Get started by running
pip install fluksio
fluksio serve
and you're ready to go.
For data science
You can turn your existing data science project into a flow by decorating your functions with @node ...
# myresearch/train.py
import fluksio
from fluksio import Port, node
@node(
requires=["dataset", Port("lr", "float")],
provides=[Port("loss", "float", stream=True), Port("weights", "artifact")],
device="gpu", device_policy="prefer",
)
def fit(dataset, lr, epochs=25):
for epoch in range(epochs):
loss = step(...)
yield {"loss": loss} # published as it happens, kept as a series
return {"weights": fluksio.save_artifact("weights.pt")}
... and passing them to a Flow:
# myresearch/pipeline.py
from fluksio import Flow, Port
from myresearch.data import prepare
from myresearch.evaluate import evaluate
from myresearch.train import fit
train = Flow("train", nodes=[prepare, fit, evaluate],
inputs=[Port("lr", "float", initial=0.01)], outputs=["score"])
Fluksio will automatically infer the order of nodes based on the inputs and outputs you defined. When everything is set, you can launch your first run as follows:
fluksio run train --lr 0.05 --wait
Checkout our documentation for more infos.
Some other features
- Flows: typed messages between nodes, wired by name, edited on a canvas or declared in code. Every change is a commit in a git repository you own.
- Runs: an experiment and a CI-style job are the same entity. Parameters, seed, result, per-node timings, artifacts and the commit it ran at.
- Dashboards: charts and controls bound to the same messages the flows carry, with no separate metrics pipeline.
- Remote workers:
pip install fluksio-workeron the GPU box; it dials out over one websocket, so nothing there has to be reachable.
Fluksio can also be used for facility automation. Visit us on fluksio.com or go straight to our documentation.
License
Copyright (C) 2026 Melvin Strobl - GNU Affero General Public License v3.0 or later. Running a modified version over a network obliges you to offer its users the corresponding source (AGPL §13).