v1.15.0: mascot reactions + post-1.14.2 cleanup#73
Merged
Conversation
The Updater preserves the previous bundle at ~/Applications/StackNudge.app.old as a rollback safety net during the swap, but nothing ever removed it after the new bundle started successfully. Result: Spotlight indexes both, and users see two StackNudge entries — the live app and a stale older version. Add Bootstrap.cleanupPostUpdateBackup, called from applicationDidFinishLaunching alongside migrateBundleNameIfNeeded. By the time we're running, launchd has brought up the new bundle and the safety net has served its purpose; recycle the .old to Trash so it stops cluttering search. Idempotent: no-op for fresh installs and already-cleaned-up users. Existing users get the fix automatically on their next update → relaunch cycle.
Two leftover-artifact bugs in the same shape as the StackNudge.app.old issue — writes that never get reversed. Updater tempdir leak: every update creates NSTemporaryDirectory()/stack-nudge-update-<UUID>/, downloads ~200 MB tarball into it, extracts the .app, and atomicSwap moves only the .app out to ~/Applications/. The tarball plus the empty tmpdir shell stayed behind forever. Failed runs (network glitch, bad checksum) leaked the whole tree including the tarball. Track tmpDir on the Updater instance; defer cleanupTempDir() in run() so success and failure paths converge. Sweep any stack-nudge-update-* leftovers from past crashes at the start of every run() so existing users' /tmp gets reclaimed on the next update attempt. Claude session sidecars: ~/.claude/sessions/<pid>.json is written by Claude Code on session start but never removed on session end. Long- running users accumulate dozens of dead-PID sidecars (13 found on my machine spanning 13 days). Stale entries risk surfacing wrong session data if a future PID collides with a dead one. Sweep at SessionStore startup with conservative guards: PID must be definitively gone (ESRCH from kill(pid, 0), not just permission-denied) and file must be ≥5 min old to avoid racing a freshly-bootstrapping session.
- PanelNav.rowCount was hardcoded to 25 + updateRowOffset but the layout
now goes to index 28 (Events section bumped the high-water mark when
it landed in 1.14.2). Down-arrow from row 24 ("Open config file…")
wrapped via % 25 → 0, jumping to the top of Settings. Bump to 29 and
document the row inventory so the next section addition doesn't
silently regress it again.
- PermissionsWindow had styleMask [.titled, .closable] but no Esc
handler; NSWindow doesn't wire cancelOperation by default. Subclass
NSWindow to perform close on cancelOperation so Esc dismisses it like
any other macOS modal.
- Expand-from-pill flicker: applyCompactLayout's full-panel branch
called setFrame(display: true), forcing an immediate AppKit paint
while SwiftUI still had CompactView in its tree (the @published flip
triggered objectWillChange but body re-eval happens on the next
runloop). Result: pill rendered into the new larger frame for one
frame. Change display:true → display:false so AppKit batches the
redraw with SwiftUI's next render pass.
…ota stress Four new reaction dimensions across all four mascots (robot, cat, eye, ghost) so the pill feels alive and informative without requiring the user to open the full panel. - Event arrival: EventStore.onAppend calls nav.reactToEvent(kind), which publishes nav.lastEventReaction for 0.8s. Each mascot reacts per-kind: stop = green flash / ears-perk / green ring / star burst; permission = yellow flash / yellow halo / yellow halo / "?" float; other = cyan pulse / whisker shimmer / cyan tick / cyan ring. - Idle micro-loops: existing TimelineViews add a slow phase that fires a tiny per-mascot animation every 9–12s — robot head-tilt, cat ear-flick, eye blink, ghost yawn (existing). Per-mascot seed offsets prevent multi-monitor sync. - Drag personality: nav.compactDragging renamed in mascots from `paused` to `dragging`. Still gates heavy TimelineView animations (original perf reason); also drives a lightweight one-shot — robot scale + glitch offset, cat vertical scale (cling), eye pupil lag, ghost translucent + trailing sparkle. - Quota stress: new computed `quotaStressed` on CompactView, true when fiveHour or sevenDay utilization >= 75. Each mascot renders a passive accent — robot sweat-drop, cat droopy frown, eye bloodshot tint, ghost translucent body. Recovers automatically when utilization drops. BotMascot signature widens with `dragging`, `eventReaction`, `stressed`; each mascot forwards them. This commit uses feat: rather than fix: so release-please bumps to 1.15.0 (rather than 1.14.3 from the surrounding cleanup fixes).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
feat:+fix:mix → release-please bumps to 1.15.0.feat(widget): mascot reactions
Four new dimensions across all mascots (robot, cat, eye, ghost):
nav.lastEventReactionandEventStore.onAppend.nav.compactDragging): robot glitch-scale, cat cling stretch, eye pupil lag, ghost translucent + trailing sparkle. Renames the mascot param frompausedtodragging(same value, broader semantics).Lifecycle leftovers (three "we write, never clean up" leaks)
Bootstrap.cleanupPostUpdateBackuprecycles it atapplicationDidFinishLaunching. Existing users get the cleanup automatically on their next update → relaunch./tmp/stack-nudge-update-*: every update created a tempdir, downloaded ~200 MB tarball, only moved the .app out at swap. Tarball + tmpdir shell stayed forever; failed runs leaked the whole tree. Fix: track tmpdir on Updater; defer cleanup inrun(); sweep stalestack-nudge-update-*from past crashes at the start of every run.~/.claude/sessions/<pid>.json: Claude Code writes these on session start but never removes them. 13 found on my machine spanning 13 days. Fix: SessionStore sweeps at startup with conservative guards (PID definitively gone viaESRCH, file ≥5 min old).Panel bugs
PanelNav.rowCountwas hardcoded to 25 but the layout goes to 28. Down-arrow from "Open config file…" wrapped via% 25 → 0, jumping to the top. Bump to 29 and document the row inventory.cancelOperationto perform close.applyCompactLayoutforced an immediate AppKit paint while SwiftUI still hadCompactViewin its tree, so the pill rendered briefly into the new larger frame. ChangesetFrame(display: true)todisplay: falseso AppKit batches the redraw with SwiftUI's next render pass.Test plan
~/Applications/StackNudge.app.oldgone after relaunch; Spotlight shows one StackNudge.ls /tmp/stack-nudge-update-*is empty after success or failure.~/.claude/sessions/doesn't contain dead-PID files >5 min old after launch.🤖 Generated with Claude Code