Commit Graph
349 Commits
Author SHA1 Message Date
stroblme c5c1d0c93e Say a dashboard is read-only in the editor's view mode too
The lock notice was on `PanelSurface` alone, so /dashboards/{name} showed
disabled controls with nothing naming the state. Same pill, not a second
one; edit mode keeps its controls live, so it stays out of there.
2026-08-25 22:15:09 +02:00
stroblme 1511d71dcd Draw the edge sparkline only for a value it can plot
The popover showed a non-numeric value twice: only numbers are recorded,
so MessageSparkline fell through to a collapsed ValuePreview and the
inspector rendered its own expanded one below it.
2026-08-25 22:13:32 +02:00
stroblme 801bab07e1 A run says whether the dashboard, the CLI or a script asked for it
`POST /runs/flows/{name}` hardcoded `cause: "api"`, so every row in the
history claimed the same origin. The body now carries an optional `cause`,
closed to the values the column knows — the dashboard sends nothing and stays
"api", `fluksio run` says "cli", and the SDK client says "sdk".
2026-08-25 22:06:29 +02:00
stroblme 7fe6a0f703 Pick the run this test made, not the first one running
The suite runs in a random order and a run left marked running by an
earlier module lands in the same list, so asserting on data[0] failed on
whichever seed put that module first.
2026-08-25 21:52:48 +02:00
stroblme cdb1bffdd4 Advisory issues read as advice rather than failure
`ADVISORY_ISSUES` moves next to `ValidationIssue` in pipeline.py, and the
model derives an `advisory` flag from its own code, so the distinction the
engine already made ships to the client instead of being re-guessed there.

The dock keeps its summary in `--destructive` only when a real fault is
among the issues and paints an advisory row `--muted-foreground`; the
canvas leaves advisories off a node's dot and border entirely, since node
status has three colours and no warning tier.

biome checks the generated `openapi.json`, which nothing formats since the
SDK script dropped its format pass — ignore it like the other generated
files.
2026-08-25 21:46:40 +02:00
stroblme b2466bfc44 Drop the unreachable 404 from save_dashboard
write_draft creates a first draft for a name that does not exist yet, so
it raises only StaleVersion — the DashboardNotFound arm never ran.
2026-08-25 21:36:02 +02:00
stroblme e23475c4bd Stop the SDK generator reformatting the whole frontend
biome.json excludes src/client, so the trailing format pass never
touched a generated file — it only rewrote unrelated sources on every
backend commit.
2026-08-25 21:33:11 +02:00
stroblme 89bb05ad13 Panel spec keeps the installation's other panels
Saving panels replaces the whole list, so the spec's setup deleted every
panel it had not created — and with it the credential of the screen on
it. It now appends to the stored list and puts the original back in
teardown, which also removes its own panel.
2026-08-25 21:30:35 +02:00
stroblme c68ad325e7 Land a paired panel on its own screen instead of a new code 2026-08-25 21:24:41 +02:00
stroblme f9de811d9c A batch flow's input is a run parameter, not a missing value 2026-08-25 18:51:54 +02:00
stroblme 4cdcf19de1 Report how long a run has been going, not just how long it took 2026-08-25 18:33:28 +02:00
stroblme c3ff7df192 Filter the icon grid, and ask before removing a panel 2026-08-25 18:33:28 +02:00
stroblme fe844c3403 Make the brand CTA visible and fix two more v3 variable classes 2026-08-25 18:33:28 +02:00
stroblme 4d5907d9ea Claim only what the cascade pool can run 2026-08-25 18:33:28 +02:00
stroblme 4e0c56d401 Start the docs site with the integrated local stack 2026-08-25 18:33:28 +02:00
stroblme b1874aacf5 Draw a wall panel at the size it was drawn for
A dashboard whose canvas matches the screen came out at 95% of it: the
panel and view routes inset the canvas by a further 16px, on top of the
margin the grid already keeps from its own edges. Two margins, one of
them costing the scale. The padding now applies only to the stacked
phone layout, which has no canvas to shrink.

A slider's tick labels are the third row of a control that has three,
and the first thing a short tile runs out of; `ticks: false` drops them
and keeps the reading and the track. The house's two sliders set it.

The 1024x600 preset is named for what it is rather than for a diagonal
it may not have — the same resolution is sold as a seven-inch panel and
as a ten-inch one.
2026-08-25 17:30:37 +02:00
stroblme 28a00a55d5 Scroll the runs table, pick a range, and choose what a comparison plots against 2026-08-25 16:32:34 +02:00
stroblme 8d5cf0a71a A cached node keeps its curve, and any input can name a run's output
A cache hit still replays no emissions — those values were the story of an
execution that is not happening — but the run they were recorded in is now
written on the row (`run_node.cached_from`), and the metrics endpoints read the
series back from there. So a reused run answers `run.metrics("train.loss")`
with the same points the run that trained did, rather than looking like a run
that produced no numbers at all. Pointed at rather than copied: a sweep of 500
reusing one frozen node would otherwise duplicate its curve 500 times.

That needed the cross-flow restore fixed first. The cache key has no flow in
it while the stored outputs are named for the flow that produced them, so
`quick.prepare` getting a hit from `train` wrote `train.dataset` into `quick`'s
state and the next node was called without its argument. One rule now covers
both halves: `requalify` reads a name owned by one flow as the same name in
another, applied to the restored outputs, to the node id behind the pointer,
and to the series names on the way out. Reuse across flows is kept.

Also: `@run:<id>.<output>` and a bare `sha256:` digest resolve on every input,
not only artifacts. Chaining a run's json config into the next one from a shell
meant pasting the whole object inline, and the CLI could not even send the
spelling — `_coerce` died in `json.loads` before the engine saw it. Both
spellings are reserved on every input now, `str` included, and `_from_run`
returns whatever the run's result holds rather than only a reference.
2026-08-25 15:12:28 +02:00
stroblme 8a645da974 Release v0.1.2
Publish / publish (push) Failing after 39s
v0.1.2
2026-08-25 12:54:41 +02:00
stroblme 3701a0d9e6 Document the runs screen, the run context and the generated dashboard 2026-08-25 12:44:19 +02:00
stroblme 9ec38d4bc3 Make a dashboard its widgets: drop the pages and sections nobody drew 2026-08-25 12:30:22 +02:00
stroblme 20a734d1ad Photograph the runs screen and a dashboard read against runs 2026-08-25 12:13:58 +02:00
stroblme 705e268863 Pin a chart to the last few runs of a flow 2026-08-25 11:52:34 +02:00
stroblme c9880cf543 Let a dashboard be read against runs instead of the live engine 2026-08-25 11:48:15 +02:00
stroblme deffd1623e Add a runs screen: a table, a run in full, and curves side by side 2026-08-25 11:44:13 +02:00
stroblme 8aee18c551 Count runs per flow, and page the run list by offset 2026-08-25 11:34:07 +02:00
stroblme caba15de81 Style the engine's own logs, notice enrolment while serving, say more in status
Four things from a testing pass.

`fluksio serve` printed its own lines through the root logger, which has no
handler and falls back to `INFO:fluksio.cloud.connector:...` — beside uvicorn's
aligned output it reads like something went wrong. The engine's loggers and
alembic's now use uvicorn's own handler. Named rather than configuring the
root: httpx logs every portal call at INFO and none of that is printed today.

`fluksio enroll` writes its config from another process, so an engine already
serving never learned it had been paired. It now looks for one every few
seconds and dials when it appears. `load()` rather than `exists()`, or a file
that does not parse would be restarted forever.

`fluksio status` says where the installation stands with its portal — never
paired, linked, or paired and unreachable, which is the one worth acting on.

`--seed` and `--timeout` had no help text at all. Both say what they are for
now, and the docs say what a seed is actually for: recorded on the run, part of
its input digest, and passed to an input named `seed` when the flow declares
one, so the number a run is labelled with is the one the code drew from.
2026-08-25 09:29:22 +02:00
stroblme e573d144c4 Let fluksio status read a local installation
`runs --local` already exists for the reason status needs it: the flows and the
history are in this directory, and looking at them should not need a server.
Watching still does — an in-process engine is the command itself, so nothing
could change under it — and says so rather than sitting on a still frame.

Found setting up a sandbox that drives the SDK entirely through --local.
2026-08-25 08:21:27 +02:00
stroblme e27080af26 Add fluksio status, and ask for a run's parameters at a terminal
Two halves of the same gap: the CLI could start work but not show you any.

`fluksio status` draws the home screen's top half in a terminal — health and
what is wrong with it, every flow with its state and node count, and the
recent runs and failures under them. `--watch` keeps it there. Rich does the
drawing; it was already installed under fastapi's own CLI, and is named now
because a command depends on it.

`fluksio run` with no parameters at a terminal asks for them, one line per
declared input with its declared value in brackets — so Enter through the lot
is what running the defaults looks like, and an artifact input takes the
`@run:` spelling the engine now resolves. A scripted run is untouched: passing
any parameter, or piping the command, skips the questions, as does --defaults.
2026-08-25 07:42:09 +02:00
stroblme 0fb1646aa8 Refuse what a node cannot publish, and stop timing out work that is fine
Four things the python SDK turned up, each fixed where every client sees it.

A key no port declares is now an error rather than a silent drop, on the
return, the yield and the emit alike — the contract the docs already stated.
The SDK reads literal yields at sync time, so a typo fails before anything
runs, and an emission of one fails the call rather than being logged where
nobody looks.

NaN and infinity are refused at the port. JSON cannot spell either, so one
that travelled came back as a 500, a socket frame that stopped the canvas, or
a metric batch the database dropped whole.

An artifact input takes `@run:<id>.<output>` or a bare digest, resolved on the
engine — so the CLI, the run dialog and a python caller mean the same thing,
and a sweep can pass one at all.

Node timeouts are off by default. The clock measured silence, which a training
node is full of, and remote workers had already stopped enforcing it — their
heartbeat reset it. Now a heartbeat proves the agent rather than the node,
ninety seconds of nothing fails the call either way, and the engine touches
work it is still running so a long node is not redelivered at sixty seconds.
2026-08-25 07:30:14 +02:00
stroblme 73da6e8e30 Keep the dev bind mounts out of CI, where they land nowhere
compose.dev.yml mounts the source over the image's own copy so `--reload`
means something in a detached local stack, and mounts the Playwright report
out to the host. Under act_runner the daemon is a sibling of the job rather
than its host: it resolves those paths against its own filesystem, finds
nothing, and mounts empty directories. So the backend reported "Path does not
exist fluksio/main.py" for a file the image plainly ships, and every shard
uploaded nothing — "No files were found with the provided path:
frontend/blob-report".

compose.ci.yml now puts backend's volumes back to what compose.yml declares
and drops Playwright's entirely; the shard copies its report out of the
container before the teardown instead.
2026-08-24 21:42:04 +02:00
stroblme 91953185ef Release v0.1.1
Publish / publish (push) Failing after 40s
v0.1.1
2026-08-24 21:33:18 +02:00
stroblme 98581ecd02 Newline at the end of the two files the hook keeps catching 2026-08-24 21:33:18 +02:00
stroblme ea5fad2fcd Stage caching for batch runs, and an engine that lives in the command
A code node in a batch run is now fingerprinted by its source, its raw
settings and the values it reads — an artifact input counting as its digest,
which is what the content addressing was always for. A run that finds the key
restores what the earlier one returned and skips the node, recorded as
`cached`. The run history is the cache: `run_node.outputs` beside the
`cache_key` the schema already had, no second store. On for code nodes, never
for the built-in and connector types that have side effects; off per node with
`@node(cache=False)` and per run with `--no-cache`.

Emissions are not replayed on a hit, so a cached training node returns its
result without redrawing its curve. Recorded in NOTEPAD.md with the two other
deliberate limits.

`fluksio run --local` boots the real app in the command's own process and
drives it through its ASGI interface behind the ordinary client, so a run no
longer needs a `serve` terminal beside it — same data directory, same history,
and the cache carries between the two. It always waits, because the engine it
starts lives exactly as long as the command.

Also: `fluksio sweep --param lr=0.1,0.01` for the product of the lists,
`run --follow` for a run's numbers as they arrive, Ctrl-C cancelling a waited
run rather than abandoning it, coloured statuses on a terminal, and `name`
made optional on the metrics endpoint so a follower can ask for every series.
2026-08-24 20:31:31 +02:00
stroblme 7f979a3f4d gc
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-24 19:26:43 +02:00
stroblme 7ddec8d2e8 Move NOTEPAD.md and ROADMAP.md to the workspace root
Consolidates deferred-work tracking now that app is going public, so no
strategy/planning content ships with it.
2026-08-24 19:16:39 +02:00
stroblme bee6ae34a7 gc
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-24 19:06:54 +02:00
stroblme b233cfa963 Bring the README back in step with the commands it gives
Four things had drifted. It told you to run `fluksio login` two lines above
saying `serve` signs you in — one of the two had to go, and it is the login.
It ran `fluksio sync` before `fluksio run`, which `run` now does itself. The
pipeline snippet passed `prepare` and `evaluate` to `Flow` without importing
them, so copying it got a NameError on the one example that matters. And the
site link was relative, which resolves to nothing on the page this file exists
to be — pypi.org.

Added one sentence, on installing into the environment you already work in,
because it is the reason a reader's own imports keep working and there is
nowhere else on this page they would learn it.
2026-08-24 18:31:51 +02:00
stroblme 7cdedded19 Three fewer things to remember
**The portal link puts itself back up.** It already retried a connection that
raised, but a session that ended *cleanly* — a portal restarting, a proxy
closing an idle socket — returned normally and went straight back round the
loop with no wait at all, so an engine could spin against a portal that was
merely saying goodbye politely. Every ending now reconnects on a delay, and
the delay turns on whether the attempt got as far as attaching: one that stood
up and dropped is a network event and retries at once, one that never stood up
waits longer each time. Jittered, so a portal coming back is not met by every
installation it serves in the same instant. Ping timeouts are named rather
than defaulted, since they are what bounds how long a suspended laptop's dead
socket looks alive, and the keepalive task is awaited so the reason a link
went reaches the log instead of the garbage collector.

**`fluksio enroll <code>`** is the whole command now; hub.fluksio.com is the
default and `--portal` names another. The one command run before anything
works should not need two flags.

**`fluksio run` syncs first.** The reason a run exists is usually the edit
before it, so remembering to sync was remembering to do something the computer
could do — including the worker refresh, which is what makes an edit to your
own package take effect at all. `--no-sync` opts out for a tight loop.

That last one needed discovery fixed: it only ever looked at top-level `*.py`,
so a repository whose code is in a package — the ordinary shape — found
nothing from its own root. It now descends into the packages it holds.
2026-08-24 18:17:16 +02:00
stroblme c872536762 One installation per project, and no login to reach it
Two things a local install should not have asked for.

`fluksio serve` now signs you in. Logging in to your own machine was a
formality — the password was printed by the same process that would have
checked it, and the database it authenticates against sits in the directory
the token goes into — so `serve` mints the token itself and says where it put
it. `fluksio login` is left for an engine somewhere else.

And an installation is `.fluksio` beside the code, found the way `.git` is,
rather than one `~/.fluksio` for the machine. A repository with its own venv
was already getting its own engine; it now gets its own flows, run history and
token too, instead of three repositories sharing one database and fighting
over one port. `--global` asks for the shared one, `--data-dir` still names
any directory, and when both exist the banner says which you are looking at
and how to reach the other.

The directory ignores itself from within — a `.gitignore` of `*`, the way uv
writes one into `.venv` — because it holds a credential and a database, and
neither belongs in anybody's history. The token is written mode 600. A login
an older version wrote to ~/.config/fluksio is still read, so nothing that
worked stops working.
2026-08-24 16:13:35 +02:00
stroblme 8f6b808f3d wording
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-24 15:12:00 +02:00
stroblme 449214c0e5 Fix the CI gates: Python 3.13, concurrency groups, hook violations
The gates have never gone green on the new runners. Three separate reasons:

- backend/Dockerfile shipped Python 3.10 while the code imports typing.Self
  and datetime.UTC, so the container exited on import and the suite could not
  even load its conftest. The image moves to 3.13 and the packages declare
  >=3.12, which is the floor the tests actually pass on; ruff's target follows
  and rewrites timezone.utc and asyncio.TimeoutError accordingly. Relocking
  drops the 3.10 branch, which bumps FastAPI and so regenerates the SDK.
- frontend/README.md had no trailing newline and two dashboard widgets used
  arbitrary text-[…] sizes. Both are em-relative on purpose, so they move to
  the inline style the neighbouring ramp already uses.
- Every commit left its own run queued: without a concurrency group a runner
  that was offline for a while works through a backlog nobody reads. A stack
  that fails to come up now prints its logs before the teardown removes it.
2026-08-24 14:55:59 +02:00
stroblme bc867897a0 Retire a worker whose pipe broke rather than hand it on
poll() still calls a child that has exited but not been reaped alive, so a
node that ended itself with os._exit left its corpse on the idle queue and
the next call died on a broken pipe. Mark it the way the out-of-step paths
already do, and _release replaces it.
2026-08-24 14:55:59 +02:00
stroblme af797ac765 Ship the files pyproject.toml points at
readme and license-files name README.md and LICENSE, but the image only ever
received pyproject.toml, so hatchling failed metadata validation the moment
the layer cache stopped hiding it:

  OSError: Readme file does not exist: README.md
2026-08-24 14:47:44 +02:00
stroblme 3604e85efb Add the Fluksio look, and make it the one a dashboard starts in
Two looks were somebody else's language spoken well, and neither was the
product's. A dashboard nobody has dressed yet should look like the rest
of the app, so there is now a third set that follows the root
DESIGN-GUIDELINES.md to the letter — `--card` surfaces told from the page
by a hairline and a low shadow rather than by colour, every control a
pill, 16px panels, frosted floating chrome, one slate-blue accent spent
on what a person can act on — and it is what `look` means when nothing
says otherwise.

That also turns the exemption the other way round. The dashboard is still
allowed to look unlike the product; it just no longer does so by default.
An existing dashboard, which has never named a look, lands on the design
it had before any of this.

Restraint is the style rather than an omission here: no ripple, no glow,
no lift, and a press answered by the colour changing. The one deliberate
departure is the selector, which holds its choice in `--primary` rather
than the `--accent` the segmented rule asks for — that is a decision
about the widget, not about the look, and a control must not change what
it signals when the drawing changes. All three sets hold it the same way.
2026-08-24 14:25:13 +02:00
stroblme 856693110f Say the licence on the two pages PyPI will show
Publish / publish (push) Failing after 42s
Every other README carries the AGPL line; these two did not, and they are the
ones that become pypi.org/project/fluksio and /fluksio-worker — the most
public surface the project has, and the place a copyleft licence is least
useful to have to go looking for. §13 is named outright for the same reason.
v0.1.0
2026-08-24 12:51:53 +02:00
stroblme a429e09176 Move the seed scripts out to the private workspace root
Several of them describe this installation specifically — its broker, its
aircon, its DMX wiring — and this repository is about to be public. The
test and client-generation scripts stay; CI depends on scripts/test.sh.
2026-08-24 12:27:30 +02:00
stroblme 32361e4ed7 Publish the connector contract and authoring guide
Adds reference/connector-contract.md and code/connectors.md to the site, with
the node-type reference finally linking the contract it describes. Strict
build passes.
2026-08-24 12:13:40 +02:00
stroblme 55b77c7ef1 Point at connectors/skeleton after the skeleton moved 2026-08-24 12:01:36 +02:00
stroblme a5a8127550 License the workspace under AGPL-3.0-or-later
Replace the empty LICENSE placeholders with the verbatim GNU AGPL v3 text,
fix the invalid "AGPLv3" SPDX string in the package metadata, and name
Melvin Strobl as the copyright holder wherever the old footers said
"Fluksio ... all rights reserved".
2026-08-24 11:53:54 +02:00