Skip to content

feat: add pause/resume/toggle to keep daemon alive without clipboard hijack#22

Merged
flamerged merged 2 commits into
masterfrom
feat/pause-resume-toggle
May 1, 2026
Merged

feat: add pause/resume/toggle to keep daemon alive without clipboard hijack#22
flamerged merged 2 commits into
masterfrom
feat/pause-resume-toggle

Conversation

@flamerged

Copy link
Copy Markdown
Owner

Summary

Short-term ergonomic fix for the "stop/start every time I want a non-AI screenshot" UX problem. Daemon stays alive (target stays selected, no restart cost); a single command toggles whether it processes screenshots.

```
sshshot pause # daemon alive, clipboard untouched
sshshot resume # back to normal processing
sshshot toggle # flip
```

`sshshot status` shows `[paused]` next to the running PID. Daemon logs the pause/resume transition once (not on every paused poll).

Why

The 0.7.x always-on clipboard hijack assumed every screenshot was bound for Claude Code / Codex on the remote. In practice ~80% of screenshots go to Slack, GitHub PRs, iMessage, etc. — where replacing the clipboard image with a path string is actively destructive. The previous escape hatch (`sshshot stop` → take screenshot → `sshshot start` → re-select target) was heavier than `scp` by hand. This makes the toggle a single command with no state loss.

This is not the long-term fix. The roadmap target is context-aware clipboard routing — replace clipboard only when the foreground app is a terminal-ish thing (Terminal/iTerm2/Warp/Ghostty/Zed/etc.) where you're about to paste into an AI agent. Queued as Phase 6b in the project memory.

Test plan

  • yarn typecheck / lint / build / format:check
  • yarn test (41 tests, no new — pause is a config-driven toggle with no pure-function logic worth a test)
  • Manual verification: `sshshot pause` → screenshot does not get processed; `sshshot status` shows `[paused]`; `sshshot toggle` flips state; `sshshot resume` brings processing back
  • CI matrix Node 20/22/24 green

flamerged added 2 commits May 1, 2026 22:33
…hijack

The 0.7.x model — daemon always rewrites the clipboard with the remote
path — assumed every screenshot was AI-bound. In practice ~80% of
screenshots go to Slack/Teams/WhatsApp/GitHub PRs/web, where the
hijack is actively destructive (you paste a path string instead of an
image). The only escape hatch was `sshshot stop` + `sshshot start` +
re-selecting the SSH target, which is heavier than `scp`-by-hand.

This adds a `paused: boolean` to config and three new commands:

  sshshot pause    — daemon stays alive, target stays selected,
                     clipboard stops being touched
  sshshot resume   — back to processing screenshots normally
  sshshot toggle   — flip between the two

Implementation:
- processNewImage() early-returns when config.paused is true. Both the
  poll-driven and fs.watch-driven paths funnel through here, so one
  guard covers both.
- A module-level `lastSeenPausedState` makes the daemon log the
  transition once instead of repeating "skipping" on every paused
  screenshot.
- `sshshot status` shows `[paused]` next to the running PID so users
  see which mode they're in at a glance.

This is the short-term ergonomic fix. The longer-term work
(context-aware clipboard routing — only hijack when the foreground
app is a terminal) is queued as Phase 6b in the project memory.
…lake

The 100-iteration uniqueness test in test/monitor-helpers.test.ts was
asserting "no collisions across 100 same-millisecond calls" against a
4-char hex suffix (16⁴ = 65,536 buckets). Birthday-paradox math says
that's a ~7% collision rate at 100 trials — unlucky but not improbable.

Round H/I/J/K/L all happened to land lucky draws on every Node version.
Round-M (PR #22) finally hit a Node-24-only bad RNG sequence and the
test failed with "expected 99 to be 100". Same code, just statistics
catching up.

Bump random suffix to 4 bytes / 8 hex chars (4.3B buckets), making the
collision probability for 100 same-ms calls ~10⁻⁶. The shape
constraint (SAFE_REMOTE_FILENAME_RE) is updated to require the new
length, and the dangerous-input test fixtures are refreshed to use
8-char suffixes so the "rejects 4-char as old shape" assertion still
documents the historical break-point.
@flamerged
flamerged merged commit 9e6d91c into master May 1, 2026
5 checks passed
@flamerged
flamerged deleted the feat/pause-resume-toggle branch May 1, 2026 20:42
@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant