Act on a run, and read Home top-down
Docs / docs (push) Successful in 23s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m18s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m49s
pre-commit / pre-commit (push) Failing after 2m5s
Test Backend / test-backend (push) Successful in 2m39s
Compose Smoke Test / test-compose (push) Successful in 33s
Playwright Tests / merge-reports (push) Successful in 1m11s

Runs: a run can now be deleted (DELETE /runs/{id}, cancelling a live one
first), exported as csv from the screen's own filters, and its flow label
opens the flow. Its "Parameters" panel became "Inputs" and lists every
input the flow declares, marking the ones that took the flow's own value
rather than the run's — the comparison table resolves the same defaults
instead of printing "unset".

Home reads brain, dashboards, health, flows: the mosaic is one full-width
scrolling strip, and the flows list and the flow-activity rollups merged
into a single left-joined table so a flow's state and its numbers sit on
one row.

Charts take a drag to narrow the x window and a double click or tap to
come back out. UplotChart holds the scale and passes resetScales:false
while a window is held, which is what the old comment said made this
impossible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LrWVRguqbk33YzfEeUx5W
This commit is contained in:
2026-08-29 08:37:11 +02:00
co-authored by Claude Opus 5
parent 4215e057d1
commit 7e506b26c0
18 changed files with 1082 additions and 365 deletions
+93
View File
@@ -432,6 +432,99 @@ def test_overview_is_not_read_as_a_run_id(client, superuser_token_headers):
assert isinstance(answer.json(), list)
# -----------------------------------------------------------------------------
# Deleting a run
#
# The route owns the four statements; what these guard is that it takes the
# children with it and refuses a run the driver is still writing to.
# -----------------------------------------------------------------------------
@pytest.fixture
def deletable_run():
"""One finished run with a node, a number and an artifact row hanging off it."""
run_id = "del-1"
with Session(db_engine) as session:
session.add(
Run(id=run_id, flow="deleted", status="ok", created_at=datetime.now(UTC))
)
session.add(RunNode(run_id=run_id, node="deleted.a", status="ok"))
session.add(RunMetric(run_id=run_id, name="deleted.loss", step=0, value=1.0))
session.add(
RunArtifact(
run_id=run_id,
name="deleted.out",
filename="out.bin",
node="a",
digest="d" * 64,
size=7,
)
)
session.commit()
yield run_id
with Session(db_engine) as session:
run = session.get(Run, run_id)
if run is not None:
session.delete(run)
session.commit()
def test_deleting_a_run_takes_its_children_with_it(
client, superuser_token_headers, deletable_run
):
"""No foreign key cascades here, so the route has to do it itself."""
answer = client.delete(
f"{settings.API_V1_STR}/runs/{deletable_run}", headers=superuser_token_headers
)
assert answer.status_code == 204
with Session(db_engine) as session:
assert session.get(Run, deletable_run) is None
for table in (RunNode, RunMetric, RunArtifact):
left = session.exec(
select(table).where(col(table.run_id) == deletable_run)
).all()
assert left == [], f"{table.__name__} rows outlived the run"
def test_deleting_a_run_that_is_not_there_is_a_404(client, superuser_token_headers):
answer = client.delete(
f"{settings.API_V1_STR}/runs/nope-1", headers=superuser_token_headers
)
assert answer.status_code == 404
def test_a_running_run_is_refused_rather_than_raced(client, superuser_token_headers):
"""The driver writes its nodes back at the end; they would have no run."""
run_id = "del-live"
with Session(db_engine) as session:
session.add(
Run(
id=run_id,
flow="deleted",
status="running",
created_at=datetime.now(UTC),
)
)
session.commit()
try:
answer = client.delete(
f"{settings.API_V1_STR}/runs/{run_id}", headers=superuser_token_headers
)
assert answer.status_code == 409
assert "Cancel it" in answer.json()["detail"]
with Session(db_engine) as session:
assert session.get(Run, run_id) is not None
finally:
with Session(db_engine) as session:
run = session.get(Run, run_id)
if run is not None:
session.delete(run)
session.commit()
# -----------------------------------------------------------------------------
# A cached node's curve
#