Skip to content

Visual flow-end feedback in OpenLoop GUI + LoopLog (state banner, frame tint, success/problem sounds, LoopLog window lifecycle) #51

Description

@maddes8cht

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)

  1. 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.
  2. 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.
  3. 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:

  1. Keep a list of spawned viewer subprocesses (Popen) in the GUI (_spawned_viewers).
  2. 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.
  3. 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.
  4. 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).
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions