Skip to content

Refresh live previews, add ⌘F search, and harden screen capture#11

Merged
peterp merged 6 commits intomainfrom
live-preview-refresh
May 1, 2026
Merged

Refresh live previews, add ⌘F search, and harden screen capture#11
peterp merged 6 commits intomainfrom
live-preview-refresh

Conversation

@peterp
Copy link
Copy Markdown
Owner

@peterp peterp commented May 1, 2026

Summary

  • Refresh tile previews on every overlay show and reconcile the grid against a fresh window list.
  • Add ⌘F search mode with progressive thumbnail resolution and arrow-key forwarding from the search field to tile selection.
  • Fix the SCScreenshotManager crash when ScreenCaptureKit returns a nil image with no error (Swift's bridged async overload force-unwraps the optional CGImage and traps).
  • Auto-restart live tile streams when ScreenCaptureKit stops them on its own (window minimised, app suspended, replayd churn, etc.). The dead stream is cleared, the last pixel buffer is promoted to a stable CGImage so the layer doesn't show a recycled IOSurface, and start() is rescheduled with linear backoff up to six attempts. The retry counter resets when fresh frames arrive.
  • Add structured per-tile diagnostics — first-delivery / first-live-frame / heartbeat / stall / skip-summary — written to /tmp/cmdcmd.log so we can tell silent SCKit failures apart from layer-update bugs.
  • Smooth the peek animation by driving shadowPath through an explicit CABasicAnimation matched to the active CATransaction (CALayer doesn't return a default action for shadowPath, so it was snapping to the destination size and painting a phantom blue halo while the tile was still small). The accent border and blue glow are also faded to zero while peeked and restored on release.
  • Silence the unused-result warning on FileHandle.seekToEnd in Log.

Test plan

  • Open the overlay over a desktop with multiple windows; previews render and refresh on each show.
  • ⌘F enters search mode; arrow keys move tile selection from inside the search field; ⌘F again or Esc exits.
  • Trigger an SCScreenshotManager nil-image case (Netflix in Firefox is a known repro) and confirm the app no longer crashes — the snapshot fail is just logged.
  • Leave the overlay open with a live tile; verify in /tmp/cmdcmd.log that heartbeats fire, stalls are reported when frames stop, and a restart is attempted (and succeeds for transient stops).
  • Hold Space to peek into the selected tile; the blue accent border and glow fade out smoothly with the zoom and the shadow stays anchored to the tile (no phantom halo above/around). Release to confirm it animates back cleanly.

🤖 Generated with Claude Code

peterp and others added 6 commits April 29, 2026 12:16
The cached CGImage from the previous close was being painted as
hasRenderedFrame=true, which short-circuited snapshot() on the next
show — so the user saw a stale frame until the live stream's first
significant-change frame arrived (or forever, for idle windows). Drop
the short-circuit so snapshot() always captures a fresh image, kick
off snapshot+stream in parallel with the show animation instead of
waiting 210ms, and stop gating the display path on the dirty-rect
threshold (keep it only for the idle-dot signal) so the tile keeps
tracking the source as frames arrive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Search mode filters tiles by substring on app name + window title;
return commits the filter, esc / Cancel clears it. Snapshot resolution
now scales with the tile's on-screen footprint (live capture unchanged)
so dense grids stay light while sparse grids look noticeably sharper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Return picks the highlighted tile, esc / Done commits the filter
(rename from Cancel matches the new behaviour), and the cardinal arrow
keys move the tile selection instead of the caret. Swallow all caret-
movement selectors so the cursor never moves inside the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Swift-async overload of captureImage force-unwraps the bridged
CGImage?, so a (nil, nil) reply from ScreenCaptureKit traps the process.
Wrap the completion-handler form in a continuation and surface a thrown
error instead, letting the existing catch log and skip the snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ScreenCaptureKit can stop a stream on its own (window minimised, source
app suspended, replayd churn, backpressure) without us recovering. The
tile was left holding a recycled IOSurface and the live preview went
dead until the overlay was re-shown.

Recover by clearing the dead stream, promoting the last pixel buffer to
a stable CGImage so the tile keeps a valid image, and rescheduling
start() with a linear backoff up to six attempts. The retry counter
resets whenever a fresh frame arrives.

Add structured logging for every transition and a per-stream watchdog so
we can tell the difference between "stream stopped", "stream alive but
only emitting idle/blank/suspended", and "stream alive, complete frames
flowing but the layer never updates". Errors are formatted with their
NSError domain/code/underlying so the cause is visible in /tmp/cmdcmd.log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
beginZoom drove layer.frame from the small grid cell to a near-fullsize
target while shadowPath snapped to the destination size at t=0, so the
blue accent shadow extended to the final dimensions before the tile had
moved — reading as a phantom halo. CALayer does not return a default
action for shadowPath, so removing the disable-actions wrapper alone
wasn't enough; the path is now driven by an explicit CABasicAnimation
that mirrors the active transaction's duration and timing function.

While peeked, the selected tile's accent border and blue glow are also
faded to zero (and restored on endZoom) so the highlight doesn't
overwhelm the near-fullscreen preview.

Also silence the unused-result warning on FileHandle.seekToEnd in Log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@peterp peterp merged commit c867f34 into main May 1, 2026
1 check passed
@peterp peterp deleted the live-preview-refresh branch May 1, 2026 13:40
peterp added a commit that referenced this pull request May 1, 2026
Cover the crash fix, live-preview auto-restart, and peek polish that
landed in #11, and extend the search changeset to mention arrow-key
forwarding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant