Docs Reference

Error handling

How LumaSync recovers from render faults — the global ErrorBoundary, the fallback card, the actions available from it, and the macOS lifecycle shutdown path.

From v1.4, LumaSync wraps every React root with a global ErrorBoundary. When a render fault bubbles out of the component tree, you get a localised fallback card — not a blank tray window, not a silent crash. The app keeps running; only the broken panel is replaced with the card.

The fallback card

Shows:

  • A short, localised headline (“LumaSync ran into a problem rendering this panel.”)
  • The underlying error message (not the stack trace)
  • An anonymised fingerprint of the error, used to correlate with the log file

Three actions sit at the bottom of the card:

  • Show logs — opens the platform log directory via open_log_dir. The full stack trace sits in the most recent log file, correlated by the card’s fingerprint. See Notifications → open_log_dir.
  • Restart — terminates and relaunches the app via tauri-plugin-process. State in ~/.config/lumasync/app.json persists across restart; in-memory session data (current mode, frame queue) is discarded.
  • Copy error — copies the error message + fingerprint to the clipboard, ready to paste into a GitHub issue or Discussion thread. Does not copy your config, credentials, or the log body — those stay on disk.

What ErrorBoundary catches

  • Render-time exceptions in React components (the common case)
  • Effect-callback exceptions that synchronously propagate during render
  • Lifecycle errors in class components (none ship today, but the boundary handles them anyway)

What it does NOT catch

  • Backend worker panics — the Rust / Tauri side. Those are logged by the Rust tracing layer and surface as a different UI state: the pipeline halts, status pills go red, no fallback card appears. Check the log file for the panic line.
  • Unhandled promise rejections outside React’s lifecycle — a background fetch that rejects without a .catch lands in the log but does not trip the boundary.
  • WebView hard crashes (rare) — those terminate the Tauri window entirely; macOS / Windows surface an OS-level crash dialog.

Filing a good issue

If the fallback card appears, attach:

  1. The Copy error clipboard contents (message + fingerprint)
  2. The most recent log file from Show logs (review first — it contains your bridge IP and local paths)
  3. What you were doing when it fired (opened Settings, switched modes, etc.)

Together those three let a maintainer reproduce in the dev console without asking follow-up questions.

macOS lifecycle hardening (v1.5.2)

The macOS shutdown path was previously fragile: Cmd+Q, the tray Quit menu, and Ctrl+C in dev each had their own teardown logic, with subtle ordering differences that could leak file descriptors, the single-instance lock socket, or the Hue DTLS sender thread.

From v1.5.2 all three paths converge on a single kick_off_shutdown_and_die Rust function gated by an atomic SHUTDOWN_FIRED flag. Two follow-ups landed alongside the unification:

  • The watchdog’s std::process::exit(0) previously bypassed Tauri plugin destroy() callbacks, which left tauri-plugin-single-instance’s /tmp/com_lumasync_app_si.sock socket on disk. The next dev launch detected the stale socket and exited within ~50 ms. The plugin is now #[cfg(not(debug_assertions))]-gated to release builds only, so dev launches don’t touch the socket and don’t leak it.
  • stop_hue_stream could block for up to 8 s under a worst-case combination of reqwest timeout (5 s) plus a DTLS sender Condvar (3 s), blowing through the 4 s watchdog budget. The call is now detached onto a worker thread and abandoned after 1.5 s — safe because the bridge times out the entertainment session server-side regardless.

End result: Cmd+Q closes the app cleanly within the watchdog budget every time, and dev rebuilds no longer fail with single-instance-lock hangs.

Type to search. Up and down arrows to navigate, Enter to open.