Skip to content

v1.16.1: build/listener/widget/update cleanups + parser tests#83

Merged
hiskudin merged 6 commits into
mainfrom
fix/1.16.1
Jun 9, 2026
Merged

v1.16.1: build/listener/widget/update cleanups + parser tests#83
hiskudin merged 6 commits into
mainfrom
fix/1.16.1

Conversation

@hiskudin

@hiskudin hiskudin commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Four fix: commits + one test: commit, all bug-only. release-please bumps to 1.16.1.

Lifecycle / hygiene

  • build.sh: re-create build/.metadata_never_index on every build so Spotlight doesn't reindex the dev artifact every rebuild, producing a confusing duplicate next to `~/Applications/StackNudge.app`.
  • EventListener.stop(): inode-guard the unlink. The post-update relaunch race had the old bundle's terminate handler removing the new bundle's freshly-created socket file out from under it — symptom: no events arriving until manual restart.

Widget

  • Cycle through active sessions: when ≥2 sessions are active and no busy session / recent event is taking priority, rotate every 5s with a soft crossfade. Snap back the moment something more urgent is on screen. Single-session users see no change.
  • UpdateChecker artifact gate: release-please creates the GitHub Release immediately on PR merge but the per-arch .tar.gz upload takes 5–15 minutes more. UpdateChecker no longer surfaces the badge until the matching artifact is present, so clicking Update can't fail with `noArtifactForArch`.

Tests

  • New `UpdateCheckerTests` covering `isNewer` (incl. the `1.10 > 1.9` semver pitfall), `stripV`, and `hasArtifact(in:arch:)` for the pre-CI-build window.
  • New `EventListenerTests` covering DTO decode, optional-field defaults, multi-line batching without trailing newline, malformed-line recovery.
  • Small refactors on `UpdateChecker` and `EventListener` to expose pure-logic surfaces (`hasArtifact(in:arch:)`, `parseEvents(_:)`) for unit testing.

Test plan

  • Build re-creates the marker; Spotlight returns one StackNudge entry.
  • Post-update relaunch: events flow into panel without manual restart (verified via socket race scenario).
  • Cycling: 2 active sessions cycle every 5s; busy/event snap-back works; single session unchanged.
  • Update gating: simulate empty assets[] → no badge; with matching .tar.gz → badge appears.
  • `swift build` succeeds; new tests compile.

🤖 Generated with Claude Code

hiskudin added 6 commits June 9, 2026 09:26
build.sh starts with `rm -rf build`, which wipes any previously-placed
`.metadata_never_index` marker. After the next rebuild, Spotlight
reindexes `build/StackNudge.app` and the user sees two StackNudge
entries — the installed bundle in ~/Applications and the dev artifact —
which is confusing right after an update.

Re-create the marker every build so the dev artifact stays out of
Spotlight regardless of how often we rebuild.
Auto-update relaunch race: new bundle's EventListener.start() unlinked
any stale socket, bound, and created the file. The old bundle's
applicationWillTerminate then ran listener.stop() and unlinked the
socket file out from under the new bundle, breaking event delivery for
the rest of the new bundle's lifetime. Symptom: after update, no events
land in the panel (no banners, no sounds, no Events tab activity) until
the panel is manually restarted.

Record the bound inode after start() succeeds; stop() now only unlinks
if the file on disk is still the one we bound to. A successor process
that has already replaced the socket keeps its file intact.
… badge

Two fixes in the user-facing update + pill loop:

- Pill: the headline ladder pinned to a single session (busiest → recent
  event → mostRecentActive). Users with 2+ parallel agents only saw the
  most-recent-active session and lost sight of the others until opening
  the panel. Insert a new branch — when ≥2 sessions are active and
  nothing more urgent is on screen, rotate through them every 5s with a
  soft crossfade. Snap back to the busy/event display the moment one
  takes priority; SwiftUI tearing down the view auto-cancels the timer.
  Pause cycling during drag so it doesn't compete with AppKit's handler.

- UpdateChecker: release-please creates the GitHub Release the moment
  its PR merges, but the release.yml build/sign/notarize/upload runs
  for 5–15 minutes after. During that window UpdateChecker advertised
  the new version but Updater couldn't find a matching .tar.gz and
  failed with noArtifactForArch. Now check the release's assets[] for
  -macos-<arch>.tar.gz before flipping nav.updateAvailable, so the
  badge only appears once the artifact is actually downloadable. The
  user just sees "up to date" until CI finishes.
…ner parser

Two pure-logic surfaces with high blast radius had no test coverage:

- UpdateChecker.isNewer / .stripV / artifact-matching: a regression here
  silently misroutes auto-update — users either never see the badge or
  click Update and hit noArtifactForArch. Cover the classic semver
  pitfall (1.10 > 1.9 numerically), the pre-CI-build window where the
  tag exists but the .tar.gz hasn't uploaded, and sha256-sidecar
  false-positives.
- EventListener wire parser: malformed JSON, missing optional fields,
  multi-line batches without trailing newline, mid-batch recovery.
  This is the most-traffic I/O path in the app — every notify.sh event
  passes through it.

Small enabling refactors:
- UpdateChecker: extract `hasArtifact(in:arch:)` from
  `hasArtifactForThisHost` so tests can exercise both archs without
  spoofing uname.
- EventListener: extract `parseEvents(_:) -> [NudgeEvent]` from
  `handleClient` so the parse pipeline is testable without spinning
  up a socket.

Skipped SessionStore.merge for now — `private` + needs deep `Session`
scaffolding; revisit in a follow-up.
parseEvents splits on \n before decoding, so a multi-line raw-string
JSON payload shreds into malformed fragments and the test sees zero
events. Reflow as one line.
@hiskudin hiskudin merged commit aa58e97 into main Jun 9, 2026
5 checks passed
@hiskudin hiskudin deleted the fix/1.16.1 branch June 9, 2026 15:38
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