Skip to content

v1.1.0 GUI hot-reload, inline unbind, shared chip system - #36

Merged
cushycush merged 5 commits into
mainfrom
gui-trigger-hot-reload
May 8, 2026
Merged

v1.1.0 GUI hot-reload, inline unbind, shared chip system#36
cushycush merged 5 commits into
mainfrom
gui-trigger-hot-reload

Conversation

@cushycush

@cushycush cushycush commented May 8, 2026

Copy link
Copy Markdown
Owner

The daemon already watches ~/.config/wflow/workflows/ via notify and rebinds chords on every KDL change. The GUI didn't. Bind a chord on the Triggers page while the editor was open on the same workflow and the editor's pinned trigger card kept showing "+ Bind a chord" until you restarted. Hand-edit a KDL file and the library snapshot stayed stale. This PR closes that gap, adds an inline x for unbinding from the editor, unifies the chip vocabulary across the editor with the library and the wflows.io site, and fixes a couple of canvas paper cuts I found along the way.

Hot-reload

Each LibraryController now spawns its own notify watcher on the workflows dir at start_watching(). Every page that has a LibraryController (Library, Triggers, Settings, the editor) refreshes its summary on FS events. The editor specifically reloads its open workflow when this workflow's chord (or its when-condition) actually changed. Comparing against last-seen avoids forcing a full editor rebuild every time some sibling workflow gets saved by another page, and saveState != idle skips the reload entirely so an in-flight edit doesn't get clobbered by the FS event from our own write.

Inline unbind

The pinned trigger card got an inline x button so unbinding doesn't require digging into the chord-capture dialog. Visible whenever a chord is bound, "Unbind chord" tooltip, one click clears.

Step-chip system

The library cards (and the wflows.io site) render trails as a pill plus a category-color dot plus an abbreviated label using shorthand (chord glyphs, shell first-token, type with quotes stripped). The editor used three different languages: GradientPill on canvas cards, CategoryIcon-plus-label stacks on the step list, icon-and-text rows in the toolbar. Pulled a single StepChip primitive and routed all four surfaces through it (canvas hero, step list rail, drag-preview ghost, toolbar). The toolbar collapses to a 3-letter code (key, txt, clk, mov, scr, fcs, wt, sh, ntf, clp, if, if!, rep, use) and expands on hover. Drove out a hover-judder bug while I was at it: the per-chip width animation was chaining against the dock's, and a short hover latch holds the dock-expanded state across the inter-chip gaps so mouse travel between chips doesn't flip the dock back to collapsed.

Row-wrap wires

Tidy-smart with two rows placed step 03 at top-right and step 04 at bottom-left. The wire between them is a true diagonal (no x or y overlap) and the axis picker fell through to its catch-all and chose horizontal, which routed back across the entire source row as a horizontal back-flow. Split the diagonal branch by direction: target-right (column-wrap) keeps horizontal so the wire arcs out the side edges, target-left (row-wrap) takes vertical so the wire exits source-bottom and U-bends down into target-top.

Canvas pan perf

Each wire is a Shape sized to the entire world Item, and the marching-ants dash effect re-rasterises that whole bbox every animation tick. Gating it on !panHandler.active and !flick.moving and root.visible means a stationary canvas (or a background editor tab) costs nothing.

Paper cuts

Two QML "Unable to assign [undefined]" warnings cleaned up while I had the files open. The community card's star pill bound visible to a logical-AND that returns undefined when stars is missing, and the canvas's wire labels bound text to a possibly undefined modelData.label. Wrapping the first in !!() and defaulting the second with || "" keeps QML's binding engine quiet. Library hot-reload log demoted from info to debug too. Three lines per launch was noise.

Tested

cargo test passes 126/0, headless smoke run via QT_QPA_PLATFORM=offscreen produces zero QML warnings, chord-bound-on-Triggers reflects in the editor's pinned card without restart, the inline x clears, smart-tidy's 2-row layout exits 03's bottom into 04's top correctly, and pan stutter is gone with dashes paused.

cushycush added 5 commits May 7, 2026 20:36
Daemon watches the workflows dir and reloads on KDL change; GUI
doesn't, so a trigger block written by another tool registers in
the daemon but the editor's pinned chord card keeps showing the
old state until the GUI is restarted. Captured for after the
recording session that surfaced it.
The daemon already watches the workflows folder via notify and rebinds
chords on every KDL change. The GUI didn't. Bind a chord on the
Triggers page while the editor was open on the same workflow and the
editor's pinned trigger card kept showing "+ Bind a chord" until you
restarted. Hand-edit a KDL file and the library snapshot stayed stale.

Each LibraryController now spawns its own notify watcher on
workflows_dir at start_watching(). When the watcher fires, the
controller calls refresh() through cxx_qt::Threading and re-emits
workflows so every page that has a LibraryController (Library,
Triggers, Settings, the editor) refreshes its summary. The watcher is
held in the rust struct so the inotify FD stays alive.

The editor wires its own libCtrl.workflowsChanged to a Connection that
reloads the open workflow when this workflow's chord (or its
when-condition) actually changed. Comparing the summary against
last-seen avoids forcing a full editor rebuild every time some sibling
workflow gets saved by another page; saveState != idle skips the
reload entirely so an in-flight edit doesn't get clobbered by the FS
event from our own write.

Also added an inline x button on the editor's pinned trigger card,
visible whenever a chord is bound, with a "Unbind chord" tooltip.
Clearing used to require clicking through to the chord-capture dialog
and hitting "Clear binding" inside; now it's one click on the card
itself.

The library hot-reload log gets demoted from info to debug while I'm
in the file. Three "library hot-reload armed" lines per launch was
noise at default log level; visible with RUST_LOG=debug if you want
it.
The wflows.io site renders workflow trails using a pill with a
category-color dot and an abbreviated label (chord glyphs, shell
first-token, type with quotes stripped). The library cards already use
it. The editor used a different language: GradientPill on canvas
cards, CategoryIcon plus label stacks on the step list, icon-and-text
rows in the left toolbar. Same product, three vocabularies.

Pulled a single StepChip primitive out of StepChipTrail and routed
everything through it. Canvas card hero, step list rail rows,
drag-preview ghost, and the left toolbar all draw the same chip now.
The toolbar collapses to a 3-letter code (key, txt, clk, mov, scr,
fcs, wt, sh, ntf, clp, if, if!, rep, use) and expands to the friendly
label on hover.

The chip pill in the toolbar stretches with the dock instead of
running its own per-chip width animation, which was the source of the
hover judder. A short hover latch holds the dock-expanded state across
the inter-chip gaps so mouse travel between chips doesn't flip the
dock back to collapsed.
Tidy-smart with two rows placed step 03 at top-right and step 04 at
bottom-left. The wire between them is a true diagonal (no x or y
overlap) and the axis picker fell through to its catch-all and chose
horizontal, which routed back across the entire source row as a
horizontal back-flow. Split the diagonal branch by direction:
target-right (column-wrap, last card of col 1 to first card of col 2)
keeps horizontal so the wire arcs out the side edges; target-left
(row-wrap, last card of row 1 to first card of row 2) takes vertical
so the wire exits source-bottom and U-bends down into target-top.

Each wire is a Shape sized to the entire world Item, and the
marching-ants dash effect re-rasterises that whole bbox every
animation tick. Gating on !panHandler.active and !flick.moving and
root.visible means a stationary canvas (or a background editor tab)
costs nothing.

Two qml "Unable to assign [undefined]" warnings while I was in there.
The community card's star pill bound visible to a logical-AND that
returns undefined when stars is missing, and the canvas's wire labels
bound text to a possibly undefined modelData.label. Wrapping the first
in !!() and defaulting the second with || "" keeps qml's binding
engine quiet.
@cushycush
cushycush force-pushed the gui-trigger-hot-reload branch from a59b47e to 8bbe72d Compare May 8, 2026 07:24
@cushycush cushycush changed the title v1.1.0 — GUI hot-reload, inline unbind, shared chip system v1.1.0 GUI hot-reload, inline unbind, shared chip system May 8, 2026
@cushycush
cushycush merged commit d62b18a into main May 8, 2026
1 check passed
@cushycush
cushycush deleted the gui-trigger-hot-reload branch May 8, 2026 07:38
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