Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,34 @@ code is the only signal, stdout / stderr ignored. Same security shape
as the existing `shell` action so it doesn't open a new attack
surface.

## Spawn-and-track action

The current pattern for "open a terminal then type into it" takes
three steps: `Shell` to launch the terminal, `WaitWindow` to block
until it maps, `ActivateWindow` to focus it. That works but has a
foot-gun: if you already have five `foot` windows open, the
title/class match in WaitWindow / ActivateWindow can land on the
wrong one. The escape valve today is to pass `--title=wflow-spawn`
when launching, which gets you back to one card per concern but
shifts the brittleness onto the title string.

A `Spawn { command, await_class, then: ... }` action variant would
collapse those three cards into one. Run the command, capture the
spawned PID, poll until a window with that PID maps, and expose it
as the focus target for the following steps so the user doesn't
need to re-name it in WaitWindow / ActivateWindow. Hyprland already
takes `pid:NNNN` as a window selector through `hyprctl dispatch`;
Sway accepts `[pid="NNNN"] focus`; KWin needs `kdotool` or a
scripting-API call. wdotool gains a matching `await-window-pid` so
the engine isn't doing per-compositor probing itself, and the
existing `WdoActivateWindow` learns a `pid:` prefix.

The win is mostly about the failure mode: today the wrong window
gets focused silently and the user discovers it when keystrokes
land in their already-open editor. Spawn-and-track makes "the
window I just opened" addressable as a real concept instead of a
title-string convention.

## Smaller polish

- Settings page: an "Advanced" disclosure for motion durations
Expand Down
84 changes: 84 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,90 @@ breaks.

---

## [1.2.0] - 2026-05-09

Editor hot-reload for full workflow content, an `else` branch with first-class
canvas affordances, and a walk-back of the unified-chip experiment in the editor.

### Added

- **The editor now hot-reloads when the `.kdl` changes on disk.**
v1.1.0 wired the library's notify watcher into the GUI but the
editor only reloaded when this workflow's chord (or its
when-condition) changed. Hand-edits to the file body, saves from
the Library page, and renames sat as stale state in the canvas
until you flipped pages. `WorkflowSummary` now carries a
`disk_mtime` field (file-system mtime, not the in-file `modified`
timestamp, which hand-edits don't bump on their own); the editor
diffs that against its last-seen value and reissues `wfCtrl.load`
when the file actually changed. The first snapshot for a workflow
is adopted without firing a reload, and FS events fired during
mid-save states (saving / saved / dirty / error) silently update
the cached mtime so the post-save tail event from our own write
doesn't re-render the canvas over what you just authored.
- **Else branches show up on the canvas, not just in the inspector.**
The data model (`else_steps`) was already there but you had to
open the inspector to grow one. The conditional card now carries
two side-by-side stub buttons in its body, **+ true** and
**+ else**, each opening the same step-kind menu the inspector
uses; wire labels read **true** / **else** instead of yes / no,
with green for the positive branch and red for the negative; the
inspector's section heading flips from **FALSE BRANCH** to
**ELSE BRANCH** to match. The new `addElseStepRequested` canvas
signal hops through the same `_addElseStep` path the inspector
already used, so the data model touches one code path regardless
of where the click came from.
- **Conditional steps' primary value is editable from the top bar.**
Editing the window name (or file path, or env name) for a `when`
block no longer requires opening the inspector's condition
section; the primary `TextField` every other step uses now drives
cond.name / cond.path. Mode (when vs unless), predicate kind, and
env's `equals=` stay in the inspector's condition section. The
redundant "1 yes / 1 else" tag that used to render in the value
pill (and overflow it on narrow widths) is gone, since the canvas
paints both branches as wires.

### Fixed

- **Step move works when the workflow has notes or conditionals.**
The rail's up/down arrows and the inspector's preceded-by /
followed-by pickers all funnel into `_moveStep`, which spliced
into `_stepsAtCrumb` (the raw KDL list) using indices from
`root.actions` (the shaped list with notes filtered and
conditional inners expanded as siblings). With either present the
shaped index could sit past the end of the raw list, the bounds
check caught the overrun, and the function silently returned. The
reading from the user's seat was "the buttons just don't do
anything." `_moveStep` now translates shaped to raw through
`_topIdx` before splicing and rejects moves whose endpoints aren't
top-level (inner-conditional cards belong to a parent's `steps` /
`else_steps` array, not the top-level sequence).
- **`+` glyph alignment on the new when-card buttons.** The 12px
glyph and the 9px label hugged the top of their Row, so the `+`
floated above the text instead of reading as one chip. Same fix
the open-import button next to it already used: anchor each Text's
`verticalCenter` to the Row.

### Changed

- **Walked back the unified step-chip across the editor.** v1.1.0
routed the canvas card hero, the step list rail rows, the drag
preview, and the left toolbar through a single `StepChip` so the
editor and the library would read as one product. Side by side
they did, but the editor needs the kind glyph more than the
library does: scanning a 2D field of cards on the canvas leans on
silhouette, and the rail and toolbar both read better with a 22px
icon than a 12px category-color dot. Reverted the canvas card
hero (and drag-preview ghost) to `GradientPill`, the toolbar to
its 56px / 200px icon-and-label rows, and the rail to its 44px
status-badge + icon + title/value layout. Library trail pills
keep `StepChip`, the surface it was designed for. The three-
letter codes in the toolbar (`txt`, `clk`, `fcs`, `ntf`, …) and
the hover-latch timer that came with the chip's width-stretch
behaviour go with the revert.

---

## [1.1.0] - 2026-05-08

GUI hot-reload, an inline unbind, and a unified chip system across the editor.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wflow"
version = "1.1.0"
version = "1.2.0"
edition = "2021"
description = "A CLI workflow engine (and GUI) for Wayland automation"
license = "MIT OR Apache-2.0"
Expand Down
10 changes: 6 additions & 4 deletions qml/components/workflow/StepInspectorPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -809,8 +809,10 @@ Item {
color: Theme.lineSoft
}

// Authoritative else-branch editor for now; canvas
// doesn't render else cards as their own column yet.
// Mirrors the canvas's else column. Editing here adds
// a card to the left side of the conditional with an
// `else` wire from the parent; the canvas's add-inner
// affordance handles the yes side.
Column {
id: elseStepsSection
width: parent.width - 48
Expand All @@ -821,7 +823,7 @@ Item {
readonly property var elseSteps: act && act.else_steps ? act.else_steps : []

Text {
text: "FALSE BRANCH (" + elseStepsSection.elseSteps.length + ")"
text: "ELSE BRANCH (" + elseStepsSection.elseSteps.length + ")"
color: Theme.text3
font.family: Theme.familyBody
font.pixelSize: 10
Expand All @@ -833,7 +835,7 @@ Item {
visible: elseStepsSection.elseSteps.length === 0
width: parent.width
wrapMode: Text.WordWrap
text: "Steps that run when the condition is false. Empty by default, add one and the engine treats this `when` as a true/false split."
text: "Steps that run when the condition is false. Empty by default; add one and the canvas grows an `else` branch from this card."
color: Theme.text3
font.family: Theme.familyBody
font.pixelSize: Theme.fontXs
Expand Down
108 changes: 59 additions & 49 deletions qml/components/workflow/StepListRail.qml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Item {
readonly property color catColor: Theme.catFor(modelData.kind)

width: parent.width
height: 34
height: 44
// Accent wash on multi-row selection so the range
// reads as one band instead of varied catColors.
color: {
Expand Down Expand Up @@ -126,58 +126,68 @@ Item {
}
}

// Row geometry: status badge on the left,
// chip filling the middle, action affordances
// pinned to the right (rendered separately
// below, so the chip can stretch under them).
Item {
id: statusBadge
width: 18
height: parent.height
anchors.left: parent.left
Row {
anchors.fill: parent
anchors.leftMargin: 14
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 8
spacing: 10

Item {
width: 18
height: parent.height
anchors.verticalCenter: parent.verticalCenter

Text {
anchors.centerIn: parent
visible: stepRow.status === ""
text: String(model.index + 1).padStart(2, "0")
color: stepRow.isActive ? stepRow.catColor : Theme.text3
font.family: Theme.familyMono
font.pixelSize: 10
Text {
anchors.centerIn: parent
visible: stepRow.status === ""
text: String(model.index + 1).padStart(2, "0")
color: stepRow.isActive ? stepRow.catColor : Theme.text3
font.family: Theme.familyMono
font.pixelSize: 10
}
Text {
anchors.centerIn: parent
visible: stepRow.status !== ""
text: stepRow.status === "ok" ? "✓"
: stepRow.status === "error" ? "✗"
: stepRow.status === "skipped" ? "·"
: ""
color: stepRow.status === "ok" ? Theme.ok
: stepRow.status === "error" ? Theme.err
: Theme.text3
font.family: Theme.familyBody
font.pixelSize: 13
font.weight: Font.Bold
}
}
Text {
anchors.centerIn: parent
visible: stepRow.status !== ""
text: stepRow.status === "ok" ? "✓"
: stepRow.status === "error" ? "✗"
: stepRow.status === "skipped" ? "·"
: ""
color: stepRow.status === "ok" ? Theme.ok
: stepRow.status === "error" ? Theme.err
: Theme.text3
font.family: Theme.familyBody
font.pixelSize: 13
font.weight: Font.Bold
CategoryIcon {
kind: modelData.kind
size: 22
hovered: rowArea.containsMouse
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 18 - 10 - 22 - 10 - 70
spacing: 1
Text {
text: modelData.summary
color: Theme.text
font.family: Theme.familyBody
font.pixelSize: Theme.fontSm
font.weight: stepRow.isSelected ? Font.DemiBold : Font.Medium
elide: Text.ElideRight
width: parent.width
}
Text {
text: modelData.value
color: Theme.text3
font.family: Theme.familyMono
font.pixelSize: 10
elide: Text.ElideRight
width: parent.width
}
}
}

// Library-style chip; same pill + dot +
// abbreviation rules as the canvas hero and
// the library trail. Action affordances are
// rendered as a sibling Row anchored right.
StepChip {
anchors.left: statusBadge.right
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 78
anchors.verticalCenter: parent.verticalCenter
kind: modelData.kind
value: modelData.editable
? (modelData.rawPrimary || modelData.value || "")
: (modelData.value || "")
height: 24
fontSize: 10
}

Rectangle {
Expand Down
Loading
Loading