v1.1.0 GUI hot-reload, inline unbind, shared chip system - #36
Merged
Conversation
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
force-pushed
the
gui-trigger-hot-reload
branch
from
May 8, 2026 07:24
a59b47e to
8bbe72d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.