Skip to content

v1.15.0: mascot reactions + post-1.14.2 cleanup#73

Merged
hiskudin merged 5 commits into
mainfrom
fix/post-1.14.2-cleanup
Jun 8, 2026
Merged

v1.15.0: mascot reactions + post-1.14.2 cleanup#73
hiskudin merged 5 commits into
mainfrom
fix/post-1.14.2-cleanup

Conversation

@hiskudin

@hiskudin hiskudin commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

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):

  • Event arrival (0.8s pulse): stop / permission / other each get a per-mascot, per-kind flash, halo, or burst — wired via new nav.lastEventReaction and EventStore.onAppend.
  • Idle micro-loops (every 9–12s): robot head-tilt, cat ear-flick, eye blink, ghost yawn. Per-mascot phase seeds prevent multi-monitor sync.
  • Drag personality (nav.compactDragging): robot glitch-scale, cat cling stretch, eye pupil lag, ghost translucent + trailing sparkle. Renames the mascot param from paused to dragging (same value, broader semantics).
  • Quota stress (≥75% on 5h or weekly): robot sweat-drop, cat droopy frown, eye bloodshot tint, ghost translucent body.

Lifecycle leftovers (three "we write, never clean up" leaks)

  • StackNudge.app.old: Updater's atomic-swap preserved the previous bundle but never removed it after the new bundle launched. Spotlight indexed both. Fix: Bootstrap.cleanupPostUpdateBackup recycles it at applicationDidFinishLaunching. 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 in run(); sweep stale stack-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 via ESRCH, file ≥5 min old).

Panel bugs

  • Settings row-nav wrap: PanelNav.rowCount was 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.
  • Esc on Permissions window: NSWindow subclass overrides cancelOperation to perform close.
  • Expand-from-pill flicker: applyCompactLayout forced an immediate AppKit paint while SwiftUI still had CompactView in its tree, so the pill rendered briefly into the new larger frame. Change setFrame(display: true) to display: false so AppKit batches the redraw with SwiftUI's next render pass.

Test plan

  • Trigger one event of each kind; mascot reacts per-kind, per-mascot.
  • Leave pill on screen ~30s; each mascot shows its idle micro-loop.
  • Drag the pill; each mascot shows its drag personality.
  • Quota stress: when fiveHour or weekly ≥75%, each mascot shows its passive stress accent; clears when utilization drops.
  • Update flow → ~/Applications/StackNudge.app.old gone 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.
  • Settings down-arrow walks every row through "Quit panel" before wrapping.
  • Settings → Check permissions → Esc closes.
  • Open panel from pill several times; no CompactView flash at large size.

🤖 Generated with Claude Code

hiskudin added 4 commits June 8, 2026 10:03
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).
@hiskudin hiskudin changed the title v1.14.3: cleanup leftovers, fix nav wrap, Esc on Permissions, expand flicker v1.15.0: mascot reactions + post-1.14.2 cleanup Jun 8, 2026
@hiskudin hiskudin merged commit f04c1d1 into main Jun 8, 2026
5 checks passed
@hiskudin hiskudin deleted the fix/post-1.14.2-cleanup branch June 8, 2026 16:17
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