Skip to content

feat(timer): show a branded alert that stays until dismissed - #648

Merged
aversini merged 1 commit into
mainfrom
feat_timer_show_a_branded_alert_that_stays_until_dismissed
Aug 14, 2026
Merged

aversini merged 1 commit into
mainfrom
feat_timer_show_a_branded_alert_that_stays_until_dismissed

Conversation

@aversini

Copy link
Copy Markdown
Collaborator

What

When the timer finished, macOS showed a banner carrying the generic Script Editor icon — a notification is attributed to whichever app posted it, and osascript is owned by Script Editor. There is no parameter to override that icon.

On macOS the timer now raises a dialog instead:

  • display dialog takes an arbitrary image, which is the only way to escape the Script Editor icon.
  • It stays on screen until acknowledged rather than fading after a few seconds.
  • It survives Focus modes and screen sharing, both of which silently suppress banners.

Pass --banner for the old passive notification. Linux (notify-send) and Windows (toast) are unchanged and always use their native banner.

Implementation notes

  • The dialog is spawned detached and unref'd, so the CLI exits immediately and never blocks anything chained behind it (timer 25m && make deploy still works).
  • spawn reports failure through an async error event, and an unhandled one is thrown — that would crash the timer the same way the old EBADARCH regression did. A no-op handler guards it, with a test pinning the behaviour.
  • display dialog has no sound parameter, so the alert sound is played alongside it via a backgrounded afplay.
  • The icon ships as assets/hourglass.png, rendered once from the committed assets/hourglass.svg. No build-time dependency; assets was added to files and the tarball contents were verified with npm pack --dry-run.
  • The icon path resolves off import.meta.url, which works unchanged from src and dist since they sit at the same depth.

Consumer impact

  • The macOS default changes from a banner to a dialog. --banner restores the previous behaviour.
  • NotifyCommand gained a required detached field.
  • Detaching means notify() can no longer report delivery for the alert path — it resolves true on release, which is all a best-effort courtesy needs to report.

Verification

  • 22 tests pass; 100% coverage across statements, branches, functions and lines.
  • tsc and the build pass.
  • Ran end to end on the real CLI: node dist/timer.js 4s exited at 5.2s while the dialog was still on screen at 7s, confirming it does not block. --banner produces the old banner.
  • Verified the dialog still renders while banners were being suppressed system-wide — a bare osascript display notification showed nothing under the same conditions.

Also fixes a typo in the README, which told you to run search --help.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EsRJCdUiefZVC2ckCbqKJ7

When the timer finished, macOS showed a banner carrying the generic
Script Editor icon, because a notification is attributed to whichever
app posted it and `osascript` is owned by Script Editor. There is no
parameter to override that icon.

On macOS the timer now raises a dialog instead. `display dialog` takes
an arbitrary image, which is the only way to escape the Script Editor
icon, and it stays on screen until acknowledged rather than fading
after a few seconds. That also survives Focus modes and screen sharing,
both of which silently suppress banners.

The dialog is spawned detached and unref'd, so the CLI exits
immediately and never blocks anything chained behind it. `display
dialog` has no sound parameter, so the alert sound is played alongside
it via a backgrounded `afplay`.

Pass `--banner` for the old passive notification. Linux
(`notify-send`) and Windows (toast) are unchanged and always use their
native banner.

Consumer impact: the macOS default changes from a banner to a dialog;
`--banner` restores the previous behaviour. `NotifyCommand` gained a
required `detached` field, and detaching means `notify()` can no longer
report delivery for the alert path — it resolves true on release, which
is all a best-effort courtesy needs to report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsRJCdUiefZVC2ckCbqKJ7
@aversini
aversini merged commit 49cdf39 into main Aug 14, 2026
5 checks passed
@aversini
aversini deleted the feat_timer_show_a_branded_alert_that_stays_until_dismissed branch August 14, 2026 03:17
@aversini aversini mentioned this pull request Aug 14, 2026
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