Session log — Zoom Apps, Shape A polish and notifications

← All session logs

Session log — Zoom Apps, Shape A polish and notifications

15 May 2026 · Hasmukh with Claude · Camera Monitor v0.4.0 → v0.7.0

Summary

Picked up from this morning's session log (footer fix, webinar support, notifications still pending) and worked through a long evening of debugging plus three polish features. Final state: Camera Monitor v0.7.0 is built and waiting for the user to test it in the morning, with all of A, B, C polish complete.

The big debugging arc was around macOS notifications. After the morning fix that moved notification firing into the main process, no banners were appearing despite the IPC chain returning true. Worked through several false leads before finding two real problems and one user-environment cause:

  1. The bot was showing in its own roster again (regression). The getCurrentUser().userId was different from the userId reported in roster events, so the original userId-based filter missed it. Added a display-name fallback filter.
  2. peer-video-state-change is unreliable in the Web SDK 3.13.2 build, but user-updated fires correctly. The peer-video-state-change was racing with user-updated and changing videoOn first, so by the time the transition detector compared the old value, it had already been overwritten as the new value. The off-to-on transition therefore looked like off-to-off, no notification fired. Solution: do all videoOn mutation AND transition detection inside upsertUser (called from user-updated), and demote peer-video-state-change to logging only.
  3. After all the code was right, banners still did not appear. Eventually traced to macOS Do Not Disturb / Focus mode being on. Toggling DND off restored notifications immediately.

After notifications were proven working, walked through the user's trajectory of A then B then C polish features. All three now built and packaged.

Decisions

  • Renderer-side detection of camera transitions is now done exclusively in upsertUser. peer-video-state-change is observation-only. Single source of truth, no race conditions.
  • Bot identity filter uses both myUserId and myDisplayName as a fallback, because the SDK's getCurrentUser() userId does not always match what shows up in roster events.
  • Session reports are written in three formats: HTML for browsing/sharing, TXT for grep/email, CSV for Excel/Sheets analysis. All three written together on Leave. Stored at ~/Documents/Camera Monitor Logs/.
  • HTML report has dark-mode-aware styling, color-coded events, per-participant total ON time summary at the bottom.
  • CSV columns chosen for analysis utility: meeting_id, bot_name, timestamp_iso, participant, event, was_on_seconds. ISO timestamps so reports across multiple meetings sort correctly.
  • Configurable notification threshold (“at least N seconds before firing”) is set in seconds, default 0 (immediate). Pending notifications cancel automatically if the camera comes back on within the window. Logs continue recording every transition regardless.
  • Snooze is a button-and-dropdown, not a checkbox. Dropdown picks duration (1m, 5m, 15m, 30m, 1h), button starts the snooze and shows a live countdown like “Cancel snooze (4:32)”. Click button again to cancel early. Snooze suppresses both off and on notifications. Logs continue.
  • Notify-on-camera-on is a separate optional checkbox, default OFF. Operator opts in if they want the symmetrical “they are back” alerts.
  • All notification preferences and the snooze duration choice persist via localStorage between launches.

Changes made

  • renderer/renderer.js: reworked upsertUser to be the sole authority for video state; demoted peer-video-state-change to logging only; added session-log infrastructure with three builders (buildLogText, buildLogHtml, buildLogCsv); added “View past logs” button; added pending-notification map with threshold delay; added snooze logic with countdown tick; added fireCameraOnNotification; added richer error logging and Zoom error-dialog text capture; added Join URL parser with stale-tk auto-clear.
  • renderer/index.html: added Join URL input, Registration token input, notify-on-camera-on checkbox, snooze button + duration dropdown.
  • renderer/styles.css: styles for num-input, checkbox-row, snooze-row, snooze button active state, dropdown.
  • main.js: added notify:camera-on IPC handler mirroring notify:camera-off. Both use Electron's main-process Notification class so the macOS bundle identity is correct.
  • package.json: bumped through 0.4.1, 0.4.2, 0.4.3, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.6.0, 0.7.0 as features landed. Final shippable: Zoom Companion/dist/Camera Monitor-0.7.0-arm64.dmg.
  • Updated project memory to reflect the v0.7.0 state and the full set of lessons learned.

Follow-ups

  • User to install v0.7.0 in the morning and verify the four polish items: notify-on-camera-on checkbox, snooze button countdown, snooze cancellation, and that all earlier features (logs, threshold, leave teardown, popup blocking, bot filtering) are still intact.
  • If everything passes, three options on the table next: distribute v0.7.0 to the studio team, tackle OBF for cross-account meetings, or move to Shape B (server-side bot). The user explicitly mentioned wanting to get to OBF.
  • If a regression appears in v0.7.0, the most likely culprits are the new snooze logic interfering with the notification chain, or the new notify-on path causing duplicate fires. The renderer logs [upsertUser], [notify], and [snooze] lines would pinpoint quickly via npm run dev.
  • Cosmetic improvement still outstanding: the txt log header column dashes are slightly misaligned with the actual data underneath. Trivial fix, not blocking anything.