Skip to content

Epic: notifications — rock-solid system v2 (tracking) #555

@Axelj00

Description

@Axelj00

Goal

When an agent needs your attention, exactly one banner fires carrying the agent's actual message. Click it → the originating tab and pane is focused. You never miss the signal. You never get noise.

That's it. That's the bar. This issue tracks getting from "current state" to that bar across all the small bugs and missing pieces that prevent it from being true today.

North-star principles

  1. One producer. OSC 9;2 from agents is the only thing that fires a system notification. Everything else (server-started, command-finished, errors) is in-app state, never a banner. (Achieved in notifications: rebuild around OSC 9;2 only — drop server-event noise, fix duplicates, make click-to-tab actually work #547 phases 1+2.)
  2. One sink. Every notification flows through NotificationManager.notifyAgentAttention. No parallel code paths that can drift.
  3. Suppress when redundant. Active focus → no banner. Mute → no banner. Already-shown-in-the-grace-window → no banner.
  4. Surface when invisible. When the user isn't watching, fire — reliably, with the agent's actual message, with a click that gets them back to the right place.
  5. Audible only via the OS. No app-side chime; the OS controls notification sound (and respects Focus / DND). (Achieved by the sound-drop commit.)

What's solid today

After #547 and the follow-up sound drop:

  • Single producer (OSC 9;2 → notifyAgentAttention)
  • No banners on Local: http://localhost:5173 or any matcher event
  • No duplicates ("Command finished in X" + "X: <msg>" was firing on every OSC — fixed)
  • Mute silences both surfaces (sidebar dot + banner)
  • Active-tab suppression
  • No Web Audio chime bypassing macOS Focus / DND
  • Web Notification API onclick works for live banners

What's still wrong, ranked by impact

# Issue Bug or gap Phase
#548 Native click handler via UNUserNotificationCenter Click from Notification Center after dismissal is unreliable — the long-standing one C
#549 Pane-scoped active-tab suppression Split-pane tabs swallow notifications for non-focused panes B
#550 Silent failure when OS permission denied User has no way to discover why nothing fires B
#551 Per-tab cooldown for runaway OSC A misbehaving agent can banner-storm B
#554 "Missed events" tray Banners are ephemeral; the message text is lost after dismissal D
#553 Settings UI Notification controls are config-file-only D
#552 Non-Claude command completion Long shell tasks in background tabs go unnotified — pre-existing gap, needs product call E

Implementation plan — phased so each phase ships independently

✅ Phase A — Foundations (done)

Phase B — Tighten the OSC path (the cheap wins)

Three small fixes, each <100 lines, each independently mergeable:

Rationale for ordering Phase B before Phase C: all three are cheap, well-scoped, testable without running the full app. They tighten the OSC path so when we replace the click handler in Phase C, the rest of the system is already correct. Doing Phase C first would mean re-validating these three after the bigger rewrite.

Phase C — Reliable click from Notification Center

The big one. Substantial Rust work (objc2 + delegate retention + authorization flow). Needs iterative testing in the running app. Once this lands, click-from-NC-after-dismissal works for the first time ever.

Phase D — Catch up on missed events

After Phase C, the click path is reliable. Now help the user see what they missed.

These two are loosely coupled — could be parallel. #554 has higher per-effort impact (the tray solves a real "what did I miss" problem); #553 is more polish.

Phase E — Optional, requires product decision

Needs a call on whether we want this at all (some users hate completion notifications), and if yes whether opt-in. See the issue for the full design discussion (PID-based vs OSC 133 shell integration).

What "done" looks like

Acceptance for the full vision (achieved when all of B + C + D ship):

  1. ✅ Claude OSC 9;2 on background tab → exactly one banner with the agent's message
  2. ✅ No banner for any non-agent event (server, error, completion of generic commands unless Phase E ships)
  3. ⏳ Click banner (live or in NC after dismissal) → focuses the correct tab + correct pane
  4. ⏳ Multi-pane tab: only the focused pane suppresses
  5. ✅ Muted tab → silent both surfaces
  6. ⏳ Permission-denied → surfaced to user with path to fix
  7. ⏳ Banner storms throttled per-tab
  8. ⏳ Missed banners recoverable via in-app tray
  9. ⏳ Settings discoverable without editing JSON

Sequencing summary

Phase A (done)
   │
   ▼
Phase B  ── #549 ── #550 ── #551  (cheap parallel-friendly fixes)
   │
   ▼
Phase C  ── #548                  (the hard one — iterative testing needed)
   │
   ▼
Phase D  ── #554 ── #553          (quality-of-life)
   │
   ▼
Phase E  ── #552                  (only if we decide we want it)

Notes

  • All phases preserve the one-producer, one-sink rule. New features add behavior on top of notifyAgentAttention, never new parallel paths.
  • Each sub-issue is self-contained and can be picked up independently. The phase grouping is a recommendation, not a hard dependency (except Phase C → Phase D, where the tray UX is much better if click is reliable).
  • macOS-only scope throughout — matches the rest of Clawterm's current targeting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-uxAI agent experience and visibilitypriority: highShould be addressed soonuxUser experience and interaction quality

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions