Skip to content

v0.14.4 - Real-time TUI Monitor Improvements (2026-02-24)

What Changed?

This release significantly improves monitor.py, the real-time TUI monitor for OSECHI detector data. The --watch mode now correctly detects file updates, skips pre-existing content on startup, and refreshes the display reliably. The internal architecture was redesigned to cleanly separate file watching, event queuing, and UI rendering into distinct layers.


What's New

Redesigned monitor.py with Reliable Watch Mode

What it does: The --watch mode monitors a directory for newly appended JSONL event lines and displays them in real time. Previously, the monitor failed to detect file updates and loaded all existing content on startup. Both issues are now resolved.

How to use it:

# Watch a directory for new detector events (default pattern: events_*.jsonl)
uv run monitor.py --watch 20260224/

# Watch with a custom file pattern
uv run monitor.py --watch 20260224/ --pattern "run_*.jsonl"

# Read from stdin (piped mode, unchanged)
uv run kazunoko read 100 | uv run monitor.py

Key improvements:

  • Startup no longer loads pre-existing file content — only lines appended after launch are shown
  • File updates are detected reliably using PollingObserver (fixes macOS FSEvents limitation)
  • New --pattern option controls which files are monitored (default: events_*.jsonl)
  • UI panels refresh correctly when new events arrive

Installation

Quick Start

# Get the release
git checkout v0.14.4

# Setup
uv sync

# Run monitor in watch mode
uv run examples/monitor.py --watch /path/to/data/directory

What's Different from the Last Version?

✅ Added

  • --pattern option for monitor.py to filter monitored files (default: events_*.jsonl)
  • register_existing_files() method in DirectoryMonitor to skip pre-existing content on startup
  • PollingObserver backend to reliably detect file changes on macOS

🔧 Changed

  • monitor.py internal architecture redesigned into four clear layers:
  • Layer 1: File watching (watchdog thread) — enqueues events only
  • Layer 2: Queue — single thread boundary between producer and consumer
  • Layer 3: Textual main thread — drains queue via set_interval, updates state, refreshes widgets
  • Layer 4: Widgets — hold reactive variables and render only
  • DirectoryMonitor now accepts a Queue instead of a callback
  • PipeMonitor class replaced with simpler _read_stdin_into_queue() function
  • All widget updates consolidated into a single _refresh_widgets() method

🐛 Fixed

  • --watch mode failed to detect file updates due to macOS FSEvents not delivering events for detector-written files (fixed by switching to PollingObserver)
  • Startup loaded all pre-existing file content instead of waiting for new lines
  • _drain_queue was a sync def passed to set_interval, which requires async def — caused the queue to never be drained and the TUI to never update
  • reactive[list] with default equality check prevented re-renders when list contents were equal (fixed with always_update=True)
  • Race condition where the watchdog thread directly mutated App state alongside the Textual main thread

Is It Safe to Upgrade?

Backward Compatible: Yes

  • monitor.py is an example script, not a library API — no downstream code is affected
  • Stdin pipe mode (kazunoko read | monitor.py) behavior is unchanged
  • The --watch flag works as before, now with correct behavior

Tests Passed

  • ✅ Builds without errors
  • ✅ Commitizen conventional commits validation
  • PollingObserver confirmed to detect file updates in examples/20260224/ during live detector run
  • ✅ Pre-existing file content skipped on startup
  • ✅ TUI panels update in real time when new events are appended

Release Details

  • Date: 2026-02-24
  • Version: v0.14.4
  • Files Changed: 1 (examples/monitor.py)
  • Commits: 0b9c6a8, dda0cd0, b271e75, cd8cc34, a997700

Next Steps

  • Consider exposing PollingObserver interval as a --poll-interval CLI option for performance tuning
  • Evaluate whether FSEvents can be re-enabled for non-detector file sources as an opt-in