New run: start a run from the app, on the working copy

The site promises simulated inputs and mocked sensor values, and nothing in
the app was that. A run already is: the values are the caller's, the state is
the run's own namespace, and nothing it computes reaches the live flow. What
was missing was a screen to do it from, and the draft flag being honoured.

`/runs/new` is a flow, a field per declared input, a seed and Run; `/runs`
stays the log. A comma-separated list in a number field expands into the grid
`fluksio sweep --param` builds and goes to the sweep route, so launching one
no longer needs a terminal. Only numbers split: a comma in a string is
content, and one in JSON is syntax.

`RunCreate.draft` was validated at submit and dropped before the run
executed, so "try the working copy" ran the published one. `Run.draft` is a
column now, the driver reads the same copy the submit checked, and a retry
carries it. `FlowSummary.mode` came with it so the rail can say which flows
are batch before one is picked.

Also here: a Retry button on a finished run, which the route has always had
and the UI never did, and parameter cells truncated to their column with the
full value on hover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TTfoK82awm8wvxXhHz3XF
This commit is contained in:
2026-09-02 15:10:24 +02:00
co-authored by Claude Opus 5
parent d471614e6a
commit 5b122341d5
20 changed files with 926 additions and 91 deletions
+25
View File
@@ -149,3 +149,28 @@ test("picked runs can be deleted", async ({ page }) => {
await expect(page.getByTestId("run-row")).toHaveCount(0, { timeout: 30_000 })
})
test("a run is started from the form", async ({ page }) => {
await page.goto(`/runs/new?flow=${flowName}`)
await page.getByLabel("epochs", { exact: true }).fill("3")
await page.getByTestId("submit-new-run").click()
// The result card is the run just started, and it fills in as it goes.
const result = page.getByTestId("run-result")
await expect(result).toBeVisible()
await expect(result.getByText("score")).toBeVisible({ timeout: 30_000 })
await expect(page.getByTestId("recent-run").first()).toBeVisible()
})
test("a list of values is a sweep", async ({ page }) => {
await page.goto(`/runs/new?flow=${flowName}`)
await page.getByLabel("epochs", { exact: true }).fill("2,3")
const run = page.getByTestId("submit-new-run")
await expect(run).toHaveText("Run 2")
await run.click()
// Submitting a sweep lands on the history, filtered to that group.
await page.waitForURL(/\/runs\?.*group=/)
await expect(page.getByTestId("run-row")).toHaveCount(2)
})