Skip to content

fix(ui): match a run's step names to the flow graph's nodes, and say why a step wrote nothing - #94

Merged
dkackman merged 2 commits into
developfrom
ui-run-state-and-unsaved-steps
Sep 13, 2026
Merged

dkackman merged 2 commits into
developfrom
ui-run-state-and-unsaved-steps

Conversation

@dkackman

Copy link
Copy Markdown
Owner

Two job-page symptoms, from a diagnosis of the Parhelion run (job 8a58c78c72d1).

The flow chart's run state never showed

Workflow.run expands for_each at dw/workflow.py:734, before it emits the workflow_start/step_start/step_end events. The flow graph, though, is drawn from the definition — where for_each is left unexpanded and a composed step is a single node. So the stream names base@open where the graph says base, and a sub-workflow's child reports its own inner step name where the graph says the composed step's. FlowView's class:active={node.name === activeStep} and the doneSteps check were comparing two names that could no longer be equal, so the active step and the finished ones stopped showing.

The same mismatch left the Progress list with no active row and no bar while a composed step ran, since a child's inner step name has no row in the list.

New ui/src/lib/runstate.ts reduces both sides to one name: a for_each member to its group (splitting at the reserved @), anything a child emitted to the parent_step the engine already puts on those events (_parent_progress_fields, dw/workflow.py:328). A group is finished only when every member is — greening it on the first would say the group is behind us while its remaining entries are still queued — and only a top-level step_end finishes a composed step, so one child inner-step no longer marks it done.

The page could not say why a step wrote nothing

groupResultFiles drops steps with no files, so a workflow that deliberately writes nothing was indistinguishable from a run whose outputs had gone missing: the steps were there, their files were not, and nothing said why. (Parhelion declares result.save: false on base/upscale/refine and no result.content_type on its six task steps, so 15 of its 16 steps write nothing by design.)

The Results panel now carries a "Steps that wrote nothing" list — one row per definition step, a for_each group collapsed with a × N badge — each with the reason read off the definition. It also appears when that list is the only thing there is to say, rather than the panel vanishing.

  • A step counts as having run when the manifest carries its entry or a top-level step_end named it, so a job still in flight never accuses a step it has not reached, and a historical job (no events at all) still reads off the manifest.
  • A sub-workflow's inner steps are filtered out — they are no node of the parent's graph, so their rolled-up manifest entries are no evidence about anything here.

Verification

  • 182 unit tests pass (15 new). The two flow tests were confirmed to fail against the old wiring — restoring currentStep/raw finishedSteps turned both red — so they are genuine regression tests.
  • npm run check, npm run lint, prettier --check clean; npm run build run, so the served ui/dist carries the change. Full Playwright e2e suite passes (92).
  • Ran the new derivations against the real Parhelion definition and manifest. Its page now reads base ×3 / upscale ×3 / refine ×3 as result.save is false…, the six task steps as result.content_type is not declared…, with only film under final/ above, and all ten graph nodes green at the end.

Not touched: the separate observation that a sub-workflow step saves its output twice (#92).

🤖 Generated with Claude Code

dkackman and others added 2 commits September 12, 2026 21:16
Workflow.run expands for_each before it emits, so the event stream names the
members (base@open) while the flow graph is drawn from the definition, which
keeps for_each unexpanded and a composed step as one node. Every comparison in
FlowView was between two names that could no longer be equal, so the active
step and the finished ones stopped showing. A sub-workflow's inner step
mismatched the same way, which also left the Progress list with no active row.

runstate.ts reduces both sides to one name - a for_each member to its group, a
child's step to the parent_step the engine already puts on its events. A group
is finished only when every member is, and only a top-level step_end finishes a
composed step.

The job page also now says why a step wrote nothing: the Results panel lists the
steps that ran and wrote no file, with the reason read off the definition
(result.save is false, or no result.content_type), and appears when that list is
the only thing there is to say. A workflow that keeps most of its steps in
memory read as a run whose outputs had gone missing.

Co-Authored-By: Claude <noreply@anthropic.com>
SVG text neither wraps nor takes text-overflow, so a sub-workflow step's
path (`templates/minimax/composable-reference-shot.json`) or a long step
name ran out of the node's right edge. Each label is now cut to a
character budget for its font with an ellipsis where it was cut - a name
from the end, a path from the start, since a path is told apart by how it
ends - and the node carries a tooltip with whatever did not fit. A
clipPath on the node catches what a wider glyph set still pushes past.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dkackman
dkackman merged commit 485a045 into develop Sep 13, 2026
6 checks passed
@dkackman
dkackman deleted the ui-run-state-and-unsaved-steps branch September 13, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant