Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions cmd/mxcli/tui/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"

tea "github.com/charmbracelet/bubbletea"
Expand Down Expand Up @@ -78,6 +79,7 @@ func newWatcher(mprPath, contentsDir string, sender MsgSender) (*Watcher, error)

func (w *Watcher) run(sender MsgSender) {
var debounceTimer *time.Timer
var debounceSeq atomic.Uint64

for {
select {
Expand Down Expand Up @@ -110,7 +112,11 @@ func (w *Watcher) run(sender MsgSender) {
if debounceTimer != nil {
debounceTimer.Stop()
}
seq := debounceSeq.Add(1)
debounceTimer = time.AfterFunc(watchDebounce, func() {
if debounceSeq.Load() != seq {
return
}
sender.Send(MprChangedMsg{})
})

Expand Down
5 changes: 3 additions & 2 deletions cmd/mxcli/tui/watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ func TestWatcherDebounce(t *testing.T) {
}
defer w.Close()

// Rapidly write 5 times — should debounce into a single message
// Rapidly write 5 times — should debounce into a single message.
// Keep the burst tighter than the debounce window so slow CI machines do
// not accidentally let an intermediate timer fire.
for i := range 5 {
_ = os.WriteFile(unitFile, []byte{byte('a' + i)}, 0644)
time.Sleep(50 * time.Millisecond)
}

// Wait for debounce to fire (500ms + margin)
Expand Down
64 changes: 64 additions & 0 deletions mdl-examples/bug-tests/349-custom-error-handler-routing.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- ============================================================================
-- Bug #349: Custom error-handler routing during microflow roundtrip
-- ============================================================================
--
-- Symptom (before fix):
-- Custom error-handler bodies that did NOT explicitly return were
-- rebuilt as DETACHED terminal paths — they got their own EndEvent and
-- never rejoined the next activity. Empty custom handlers could either
-- lose their error flow entirely or rejoin into statements that read
-- an output variable absent on the error path.
--
-- Root cause:
-- The microflow builder handled custom error handlers immediately at
-- the source activity. It did not retain pending handler state until
-- the next safe continuation was known, so a later handler could
-- overwrite an earlier pending handler.
--
-- After fix:
-- - Pending custom-handler state is queued.
-- - Non-terminal handler bodies rejoin through a merge before the
-- next safe continuation, instead of fabricating detached EndEvents.
-- - Empty custom-handler error flows are preserved.
-- - Output-producing handlers terminate before output-dependent
-- continuation in void microflows.
--
-- Usage:
-- mxcli exec mdl-examples/bug-tests/349-custom-error-handler-routing.mdl -p app.mpr
-- mxcli -p app.mpr -c "describe microflow BugTest349.MF_Caller"
-- `mx check` against the resulting MPR must report 0 errors and the
-- non-terminal handler must rejoin the continuation activity.
-- ============================================================================

create module BugTest349;

create microflow BugTest349.MF_RefreshData (
$Token: string
)
begin
log info node 'BugTest349' 'refreshed: ' + $Token;
end;
/

create microflow BugTest349.MF_NextBatch ()
begin
log info node 'BugTest349' 'next batch';
end;
/

-- Caller with non-terminal custom error handler. The handler body logs
-- but does not return, so its tail must rejoin the continuation
-- (`call microflow ... MF_NextBatch ()`) instead of becoming a detached
-- terminal path.
create microflow BugTest349.MF_Caller (
$Token: string
)
begin
call microflow BugTest349.MF_RefreshData (Token = $Token)
on error without rollback {
log error node 'BugTest349' 'refresh failed';
};

call microflow BugTest349.MF_NextBatch ();
end;
/
Loading