Summary
Give the OpenLoop GUI (ui/app.py) and the standalone LoopLog viewer (tools/looplog.py) a clear, shared visual state indicator so a finished (or interrupted) flow is immediately noticeable at a glance. Both windows should present the same state signal in the same form. Additionally, make the GUI manage the LoopLog viewer windows it spawns itself, so a resumed/restarted run always shows a fresh, correctly-watching viewer.
State model
Both windows display one of the following states:
| State |
Meaning |
Color |
running |
Flow is currently executing |
Blue |
completed |
Flow finished successfully |
Green |
interrupted |
Flow stopped, resumable (timeout, stop, max_loops, ...) |
Amber |
error |
Flow aborted with an error (agent_error, missing state, ...) |
Red |
idle |
No flow active |
Gray |
- Success vs. problem is decided by the run's
termination_reason (from the engine state / final log summary): completed -> success; anything else (interrupted/error) -> problem.
- Amber for "interrupted / resumable" is the agreed distinction (not just green vs. red).
- On any state transition, the new state is flashed briefly (2-3 frames) so the change catches the eye even out of the corner of the user's attention.
Visual indicators (applied identically in both windows)
- In-window status banner (primary, cross-platform): a thin colored accent strip at the top of the window + the window title prefix shows the state label (e.g.
● RUNNING, ● COMPLETED, ● INTERRUPTED, ● ERROR). The banner must be identical in look and color across OpenLoop GUI and LoopLog.
- Native window frame tint (Windows enhancement): tint the OS title bar / window border in the same state color via
DwmSetWindowAttribute (DWMWA_BORDER_COLOR) using ctypes (still zero-dependency). Guarded: if unsupported (non-Windows or API failure), the status banner remains the indicator.
- Flash animation on transition (2-3 frames) - e.g. alternate the banner color / toggle a bright state for a short period, in both windows the same way.
System sounds
Play a Windows system sound on flow finish (stdlib only, zero-dependency; no-op when unavailable):
- Success (
completed): success/chime-like sound (e.g. MessageBeep(MB_OK)).
- Problem (
interrupted, error): warning/error sound (e.g. MessageBeep(MB_ICONERROR)).
Sounds must be triggered by the actual completion detection (not by a background timer).
How each window knows the state
- OpenLoop GUI: already receives authoritative state via its
state_callback and the run finish via __done__ + termination_reason. Derive running/completed/interrupted/error/idle from engine state during and after the run. On _execution_done() apply the final state.
- LoopLog: derive the completion from the log structure it already watches: last line
</openloop_log> = flow ended; parse the final state / summary to decide interrupted vs completed. LoopLog must keep showing the status banner even after it stops polling (it currently stops watching - see below).
LoopLog viewer lifecycle (GUI-side, #48 follow-up)
Rather than letting an old viewer linger (stale, no longer refreshing), the OpenLoop GUI tracks every LoopLog process it spawns and re-manages them:
- Keep a list of spawned viewer subprocesses (
Popen) in the GUI (_spawned_viewers).
- On a new run - Start Execution or Continue - close all previously spawned viewers managed by the GUI, then spawn a fresh viewer for the active log.
- Only close viewers that OpenLoop spawned itself (auto-open at run start + the "LoopLog" toolbar button). Manually started LoopLog windows (launched by the user outside OpenLoop, or against a different log) are never touched.
- Closing procedure: a graceful close, i.e. find the spawned window (
EnumWindows + GetWindowThreadProcessId -> WM_CLOSE, guarded; fallback to terminate() when the window handle cannot be found or on non-Windows).
- A new viewer is opened normally after the old ones are closed, so the new one watches and the old one never lingers frozen.
Tests
- LoopLog: completion detection (last-line
</openloop_log>), interrupted-vs-completed classification, banner state + flash not breaking refresh.
- GUI: viewer spawn tracking (without spawning real windows where possible), old spawned viewers closed on new Start/Continue, manually-opened LoopLog untouched, state mapping to colors, sound invocation on completion (guarded / no-op when no sound available).
Open questions / decisions to confirm before implementing
- Exact classifier for
interrupted vs error: allowlist of resumable reasons -> amber, everything else -> red (proposed).
- Whether the success/problem sound selection should be configurable or fixed stdlib defaults.
Summary
Give the OpenLoop GUI (
ui/app.py) and the standalone LoopLog viewer (tools/looplog.py) a clear, shared visual state indicator so a finished (or interrupted) flow is immediately noticeable at a glance. Both windows should present the same state signal in the same form. Additionally, make the GUI manage the LoopLog viewer windows it spawns itself, so a resumed/restarted run always shows a fresh, correctly-watching viewer.State model
Both windows display one of the following states:
runningcompletedinterruptederroridletermination_reason(from the engine state / final log summary):completed-> success; anything else (interrupted/error) -> problem.Visual indicators (applied identically in both windows)
● RUNNING,● COMPLETED,● INTERRUPTED,● ERROR). The banner must be identical in look and color across OpenLoop GUI and LoopLog.DwmSetWindowAttribute(DWMWA_BORDER_COLOR) usingctypes(still zero-dependency). Guarded: if unsupported (non-Windows or API failure), the status banner remains the indicator.System sounds
Play a Windows system sound on flow finish (stdlib only, zero-dependency; no-op when unavailable):
completed): success/chime-like sound (e.g.MessageBeep(MB_OK)).interrupted,error): warning/error sound (e.g.MessageBeep(MB_ICONERROR)).Sounds must be triggered by the actual completion detection (not by a background timer).
How each window knows the state
state_callbackand the run finish via__done__+termination_reason. Deriverunning/completed/interrupted/error/idlefrom engine state during and after the run. On_execution_done()apply the final state.</openloop_log>= flow ended; parse the final state / summary to decideinterruptedvscompleted. LoopLog must keep showing the status banner even after it stops polling (it currently stops watching - see below).LoopLog viewer lifecycle (GUI-side, #48 follow-up)
Rather than letting an old viewer linger (stale, no longer refreshing), the OpenLoop GUI tracks every LoopLog process it spawns and re-manages them:
Popen) in the GUI (_spawned_viewers).EnumWindows+GetWindowThreadProcessId->WM_CLOSE, guarded; fallback toterminate()when the window handle cannot be found or on non-Windows).Tests
</openloop_log>), interrupted-vs-completed classification, banner state + flash not breaking refresh.Open questions / decisions to confirm before implementing
interruptedvserror: allowlist of resumable reasons -> amber, everything else -> red (proposed).