v0.2.0: field-test fixes, update resilience, support file #4

Merged
aporter merged 9 commits from feat/v0.2.0-hardening into main 2026-11-03 21:49:00 +10:00
Owner

v0.2.0: field-test fixes, update resilience, support file

Fixes from the first field test and the follow-up reviews, tested end to end on the Windows 11 VM.

Field-test problems

  • Quick presses no longer queue: presses during a switch (and for 1 s after) are ignored; the key shows WORKING.
  • JW Library's media window minimised or hidden (suspended Store app): the key shows BACK TO JW and a press brings it back.
  • Zoom ignoring asynchronous moves (seen in one Zoom session; 0.1 could not switch at all there): bounded direct-move fallback.
  • Refusals show a short reason on the key (drawn into the image); readiness badge before anyone presses; warnings while covering.

Safety and recovery

  • Every window re-identified before each change; "unreadable" never treated as "gone"; Zoom and JW restored independently; display-change policy; JW recovery capped at 4 s.
  • No waiting call is ever made on a Zoom that doesn't answer within 250 ms (SendMessageTimeout probe); the undo record is kept and JW is still restored.
  • Settings store: serialised, 3 s timeouts, journal validated; 0.1 undo records load.

Resilience

  • Diagnostics and a one-click redacted support file (Desktop, shown in Explorer).
  • Detection overrides as dropdowns filled from the PC; second detection paths: JW Library rebuilt as a desktop app, and a Zoom fallback signal when the toolbar class disappears.
  • Settings panel fixed (it never received the plugin's replies, also in 0.1) and redesigned.

Testing

  • 115 unit tests (fault injection at every step, crashes, display changes, stale records, hung Zoom, timing caps).
  • Windows VM (Zoom 7.2.5, JW Library 15.10.9, Stream Deck 7.6): upgrade from 0.1 while covering, matrix 10/10, and the full lifecycle suite through real key presses, including a real reboot while covering. See testing/results/vm-lifecycle-2026-11-03.md. Demo: videos/jwzoom-v0.2.0-demo-1920x540.mp4.
  • Not verified in the VM: handing keyboard focus back after JW is re-activated (nothing could take the foreground there); elevated Zoom/JW (UAC is off in the VM; unit-tested).

Review-gate: gpt-6.1-sol fix-then-ship (d8b9c90..0c7b972: 2 blocking hang risks fixed in 0c87911; re-check found the rollback path, fixed in aaefb18; final re-check found 2 restore-path gaps, fixed in 738ac7a with regression tests, not re-reviewed)

🤖 Generated with Claude Code

## v0.2.0: field-test fixes, update resilience, support file Fixes from the first field test and the follow-up reviews, tested end to end on the Windows 11 VM. **Field-test problems** - Quick presses no longer queue: presses during a switch (and for 1 s after) are ignored; the key shows WORKING. - JW Library's media window minimised or hidden (suspended Store app): the key shows BACK TO JW and a press brings it back. - Zoom ignoring asynchronous moves (seen in one Zoom session; 0.1 could not switch at all there): bounded direct-move fallback. - Refusals show a short reason on the key (drawn into the image); readiness badge before anyone presses; warnings while covering. **Safety and recovery** - Every window re-identified before each change; "unreadable" never treated as "gone"; Zoom and JW restored independently; display-change policy; JW recovery capped at 4 s. - No waiting call is ever made on a Zoom that doesn't answer within 250 ms (SendMessageTimeout probe); the undo record is kept and JW is still restored. - Settings store: serialised, 3 s timeouts, journal validated; 0.1 undo records load. **Resilience** - Diagnostics and a one-click redacted support file (Desktop, shown in Explorer). - Detection overrides as dropdowns filled from the PC; second detection paths: JW Library rebuilt as a desktop app, and a Zoom fallback signal when the toolbar class disappears. - Settings panel fixed (it never received the plugin's replies, also in 0.1) and redesigned. **Testing** - 115 unit tests (fault injection at every step, crashes, display changes, stale records, hung Zoom, timing caps). - Windows VM (Zoom 7.2.5, JW Library 15.10.9, Stream Deck 7.6): upgrade from 0.1 while covering, matrix 10/10, and the full lifecycle suite through real key presses, including a real reboot while covering. See `testing/results/vm-lifecycle-2026-11-03.md`. Demo: `videos/jwzoom-v0.2.0-demo-1920x540.mp4`. - Not verified in the VM: handing keyboard focus back after JW is re-activated (nothing could take the foreground there); elevated Zoom/JW (UAC is off in the VM; unit-tested). Review-gate: gpt-6.1-sol fix-then-ship (d8b9c90..0c7b972: 2 blocking hang risks fixed in 0c87911; re-check found the rollback path, fixed in aaefb18; final re-check found 2 restore-path gaps, fixed in 738ac7a with regression tests, not re-reviewed) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Field-test fixes and update resilience, per the reviewed plan:

- Recovery: re-identify every window immediately before each change;
  distinguish gone / replaced / unreadable (unreadable keeps the undo
  record); restore Zoom and JW independently, even if one throws;
  display-change policy; late async moves re-checked; journal validated
  semantically on load; per-press numbered transaction log.
- Settings: single owner of global settings, serialised transactions,
  3 s timeouts -> "Check" state that never starts a fresh switch.
- Key: presses during a switch are dropped (WORKING image, 300 ms
  cooldown); refusals show a short reason; readiness poll (amber badge)
  and covering health warnings that never move a window.
- Support: "Save support file" writes one redacted .txt (diagnostics +
  recent logs) to the Desktop and selects it in Explorer; "Show
  diagnostics" with Copy.
- Detection overrides (Zoom classes, toolbar class, JW title) with
  Test / Save / Reset; undo never uses them.
- Minimised Zoom main / JW media windows: specific refusals for now.

Tests: 86 unit tests (was 37), incl. crashes at every step, fault
injection, display changes, stale records after reboot.

Review-gate: gpt-6.1-sol fix-then-ship -> 5 blocking fixed, re-check confirmed; final redaction/blocked/display-note gaps fixed

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Full VM run on Windows 11 (Zoom 7.2.5, JW Library 15.10.9): harness matrix
10/10, upgrade from 0.1 while covering, and a lifecycle suite through real
key presses (Stream Deck first/middle/last, killed while covering, real
reboot while covering, JW restarted, Zoom leaving, displays unplugged).
Results in testing/results/vm-lifecycle-2026-11-03.md.

Fixes found by the run:
- Cooldown 300 ms -> 1 s, WORKING shown throughout: a switch takes ~100 ms,
  so a triple tap ~250 ms apart flipped everything back.
- Short reasons are drawn into the key image: key titles can be hidden
  (the rig profile hides them) and then the reason was invisible.
- Clearer note when JW's old media window is gone; readiness and key-view
  changes are logged; no "x" multiplication sign in log messages.
- Harness: step arguments may contain colons.

Test rig: lifecycle.ps1, CCD display helpers (ccd.ps1, ccd-attach.ps1),
record-v2.ps1 + vf540-v2.txt, finish.ps1 parameters, rig gotchas.
Demo: videos/jwzoom-v0.2.0-demo-1920x540.mp4.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
When JW Library's media window gets minimised by mistake (nothing on the
TV), the key no longer refuses with "JW min". It shows BACK TO JW, and the
next press only un-minimises JW Library's media window (with the existing
uncloak handling); Zoom isn't touched and no undo record is written.

The minimised media window is recognised only when exactly one minimised
JW frame would restore to exactly fill a display and isn't flagged to
restore maximised (VM evidence: it loses its JWLibrary.exe child but keeps
its monitor-sized restore rect; the main window keeps its own size or the
restore-to-maximised flag). Anything less certain is still "JW min".

VM: new scenarios jwmin and jwmin-long (20 s, suspended app) pass through
real key presses; toggle and triple-tap re-run pass. Demo re-recorded.
93 unit tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the external code review (validated against the code):
- listZoomWindows checks the window class before opening any process on
  the readiness/press path; the unfiltered scan stays for diagnostics.
  children() uses the per-scan process cache. bench now compares both.
- JW recovery is capped (default 4 s in total); before, the suspended-app
  path could keep WORKING on screen for over 20 s.
- cover.ts split: window-ops.ts (identity checks, waits, transaction log),
  jw-recovery.ts, health.ts. Behaviour unchanged.
- Separate QUIET_AFTER_PRESS_MS instead of reusing POLL_MS.

Found in the VM while taking screenshots:
- In one Zoom meeting session Zoom ignored every SWP_ASYNCWINDOWPOS move,
  so the switch failed (safely). Now async first (0.5 s), then a direct
  SetWindowPos, never on a hung Zoom (IsHungAppWindow).
- The settings panel never received the plugin's replies: it compared
  them with its own connection id instead of the key's context (already
  broken in 0.1). It now asks for its data once connected and keeps the
  status line live.

Settings panel redesign: compact rows (status, last press, support), and
the Advanced detection fields are dropdowns filled with what this PC
shows (Zoom window classes, child classes only one meeting window has,
JW Library frame titles), plus Default and Custom.

99 unit tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- JW Library: second detection path for a rebuilt (non-Store) JW Library:
  a visible window titled like JW Library whose own process is
  JWLibrary.exe and that exactly fills a display. Used only when no Store
  frame matches; JW's detached inner CoreWindow (cloaked, monitor-sized,
  seen in the VM while the media frame is minimised) is never taken.
- Zoom: when no meeting window has the toolbar class, a fallback signal
  identifies the main window if it has at least two of ZPMeetingInfoClass,
  VideoPaginationBarWndCls, ZappLeftMeetingWindow and the other none;
  otherwise it refuses as before. The log and diagnostics say which signal
  and which JW path were used.
- A JW Store media frame that fills a display but is restored-yet-cloaked
  (suspended app; happened by itself in the VM) counts as hidden: BACK TO
  JW, and the press brings it back like a minimised one.
- VM rig: stand-in desktop JW Library (fakejw.ps1), lifecycle scenarios
  jw-desktop and zoom-fallback, harness --toolbar override.

109 unit tests. VM run (in progress): upgrade 0.1->0.2 while covering,
harness matrix 10/10, toggle, triple, JW minimised (short and long), Zoom
main minimised, JW desktop path, Zoom fallback, Stream Deck killed while
covering, Stream Deck started last, both display-unplug cases: all PASS.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the tester's 0.1 log (every switch-back needed JW's re-activation and
focus was never handed back; queued presses replayed every ~5 s):
- Focus hand-back: plain SetForegroundWindow, then AttachThreadInput, then
  SwitchToThisWindow, then the old Alt tap. Not verifiable in the VM (no
  method could take the foreground from JW's full-screen window there);
  the log now says which window had focus and where it went.
- JW recovery switches to the activating restore as soon as the window is
  back but cloaked, instead of first waiting a full settle (~1.5 s saved
  per switch-back on a PC where JW gets suspended).

Full VM re-run on the final build (results file updated): upgrade from
0.1 while covering, harness + matrix 10/10, toggle, triple tap, JW
minimised/hidden (incl. after boot), JW desktop path, Zoom fallback, Zoom
main minimised, Stream Deck first/middle/last/killed, Zoom leaving, JW
restarted (normal close), real reboot while covering, unrelated display
unplugged: all PASS. Rig: graceful JW close, harness refocus step,
upall-style relaunch notes. Docs: second detection paths, TV re-plug note.

110 unit tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Independent review (gpt-6.1-sol) of d8b9c90..0c7b972, verdict fix-then-ship:
- [P1] Focus hand-back attached input queues and called BringWindowToTop,
  which can block the plugin's only thread if that app stops responding.
  Back to the plain SetForegroundWindow plus the Alt-tap fallback (as in
  0.1); none of the extra methods worked in the VM anyway.
- [P1] IsHungAppWindow only reports a hang after ~5 s, so it didn't make
  the direct SetWindowPos safe. Replaced by a bounded probe:
  SendMessageTimeout(WM_NULL, SMTO_ABORTIFHUNG, 250 ms) right before the
  direct move; otherwise the switch is rolled back.
- [nit] Diagnostics keep meeting-class Zoom windows whose process can't be
  read, so the support file says "blocked" like the key does.

111 unit tests; VM sanity: harness switch 75 ms / back 2.1 s, toggle and
JW-hidden scenarios pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Re-check (gpt-6.1-sol): after a failed responsiveness probe, the rollback
still called setStyle/setPlacement on the same unresponsive Zoom.
- Zoom is probed (SendMessageTimeout WM_NULL, 250 ms) before every call
  that waits on its thread: the pre-move placement, the frame change, the
  direct move, the switch-back frame/placement and the late re-apply.
- A rollback before Zoom was changed leaves Zoom completely alone (nothing
  is owed); after a change, an unresponsive Zoom is deferred with the undo
  record kept while JW is still brought back, and the next press finishes.

113 unit tests (new: no Zoom call at all after a failed probe; Zoom hangs
while covering -> JW back, Zoom deferred, next press restores exactly).
VM: matrix 10/10, toggle, triple tap, JW hidden all pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Restore path: re-probe Zoom before placement; late mismatch keeps the record
All checks were successful
ci / secrets (pull_request) Successful in 9s
ci / build (pull_request) Successful in 29s
738ac7ab7b
Final re-check (gpt-6.1-sol) found two more restore-path gaps:
- [P1] The switch-back placement call wasn't re-probed after the frame
  change; Zoom hanging in between could block the plugin. Now probed
  again; on failure the record is kept and JW is still restored.
- [P1] If a late move landed and Zoom stopped responding, the restore
  reported success and cleared the record while Zoom still covered the
  TV. A late mismatch now always fails the Zoom part (record kept).

Both reproduced as tests (115 pass). VM: matrix 10/10, toggle and JW
hidden pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
aporter/jwzoomswitcher!4
No description provided.