Two bugs conspired to produce "check the logs" toasts with an empty log viewer:
1. The log viewer (both SSE stream and REST endpoint) only queried
ledmatrix.service via journalctl. Web API errors are logged by the
Flask process running as ledmatrix-web.service, so they never
appeared in the viewer. Add -u ledmatrix-web.service to both calls;
also add --output=short-iso so timestamps from the two services
sort cleanly when interleaved. Use shutil.which-resolved absolute
paths for sudo/journalctl (S607 compliance) in api_v3.py; fall back
to known Pi paths if which returns None.
2. app.py: resolve journalctl and systemctl to absolute paths via
shutil.which at module init (_JOURNALCTL, _SYSTEMCTL). Replace bare
names in logs_generator() and the cached systemctl is-active check.
Guard both sites: logs_generator yields a clear SSE error message
and sleeps 60 s if journalctl is not found; the systemctl block is
skipped entirely if systemctl is not found, leaving the cache at its
last-known value.
3. When execute_system_action() ran a systemctl command that returned
non-zero, only the return code was logged — result.stderr was
silently discarded. Log it at ERROR level and include returncode and
stderr in the JSON response so callers get actionable failure details.
Same fix applied to the early-return start_display branch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>