Skip to content

feat: notify when a chat needs input (desktop + web) - #14

Merged
radroid merged 6 commits into
mainfrom
t3code/chat-input-notifications
Jul 26, 2026
Merged

feat: notify when a chat needs input (desktop + web)#14
radroid merged 6 commits into
mainfrom
t3code/chat-input-notifications

Conversation

@radroid

@radroid radroid commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Closes #11

What

Fires a notification when a chat transitions into a state that needs you — approval requested, agent question (waiting for input), or turn finished — on both surfaces:

  • Desktop (Electron): native OS notification from the main process. Clicking it reveals/focuses the window and navigates to the chat.
  • Web (desktop or mobile browser): Web Notifications API from the client (works for background tabs, e.g. phone browser over Tailscale). Clicking focuses the tab and navigates to the chat.

How

  • Detection rides the existing projectThreadAwareness() phase ladder (@t3tools/shared/agentAwareness) — the same source of truth the mobile push pipeline uses. A pure edge-detecting tracker (apps/web/src/notifications/needsAttention.logic.ts) diffs phases across thread-upserted shell updates: fires on transitions into waiting_for_approval / waiting_for_input, and on running → completed only (which also kills the spurious "Done" at thread birth). First observation of a thread never fires, so reloading with an already-waiting thread stays silent.
  • One coordinator for both surfaces: Electron renders the same web bundle, so a single <NotificationCoordinator/> in the root route watches all threads across all connected environments and dispatches through a platform shim — desktopBridge.showNotification(...) when present, else new Notification(...) (guarded: Android Chrome's page-scope constructor throw is swallowed).
  • Desktop IPC: new desktop:show-notification / desktop:notification-activated channels, an ElectronNotification Effect service (coalesces per-thread via the request id, retains live references), preload bridge members typed in @t3tools/contracts (optional, so older shells hosting newer bundles degrade to the web path).
  • Suppression: no notification when the tab/window is focused and that exact chat is on screen (including the draft route right after promotion). Different chat on screen → still notifies.
  • Settings: notifyOnNeedsInput client setting (default on), row in Settings → General ("Notify when an agent needs input"). On web, enabling requests browser permission from the toggle gesture; a denied permission shows an inline hint; a once-per-session toast nudges permission when a notification would have fired but permission was never asked.

Out of scope (per issue): Web Push for closed tabs (needs service worker + push infra; iOS Safari additionally requires an installed PWA).

Testing

  • Unit: 40+ new tests across the tracker (every behavior rule + mutation-tested edges), the notifier shim (platform routing, constructor-throw guard), coordinator pure helpers (input building/dedupe, activation params), permission-prompt truth table, contracts decode defaults, and the Electron service/IPC method (fake-Electron harness: click→reveal→broadcast, coalescing, unsupported guard). Full suites green: web 172 files / 1505 tests, desktop 53 / 377, contracts 202. Typecheck + lint clean on all touched packages.
  • Live end-to-end (real browser, real Claude CLI turns, isolated --home-dir instance): turn-completion and agent-question notifications fire exactly once with correct title/body/tag while the tab is genuinely blurred; startup grace on reload; suppression when viewing the firing chat (and the converse — other-route transitions do notify); settings toggle gates and persists; notification click navigates to the right thread. Re-smoked after the final fix wave.
  • Desktop: pnpm build:desktop + pnpm test:desktop-smoke pass. (Native notification click path is unit-tested via fakes; not OS-driven e2e.)

Review process

Built task-by-task with per-task spec+quality reviews, a final whole-branch review, and a verified fix wave (notably: Android Chrome constructor guard, desktop Notification Center coalescing, draft-route suppression). Known accepted edges: notifications for since-deleted threads navigate to a dead route; a macOS click with zero open windows doesn't recreate one; permission state in the settings panel refreshes on interaction, not on browser-chrome changes.

🤖 Generated with Claude Code

radroid and others added 6 commits July 25, 2026 09:53
…ification surface

Adds the `notifyOnNeedsInput` client setting (default on, opt-out) to
`ClientSettingsSchema` and `ClientSettingsPatch`, plus the renderer-facing
`DesktopNotificationRequest` / `DesktopNotificationActivation` types and the
optional `showNotification` / `onNotificationActivated` members on
`DesktopBridge`. Both bridge members are optional like `preview` so a newer
web bundle can run inside an older desktop shell and feature-detect.

The desktop `ClientSettings` test fixture gains the new required field so the
repo still typechecks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…avigate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…top coalescing, draft-route suppression

Guard `new Notification(...)` against Android Chrome's illegal-constructor
throw, coalesce desktop notifications by request id, resolve the active
thread through the draft route, hide the denied hint while the toggle is
off, and pin the shim's platform routing with tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 414baf24-9726-445c-833e-17e65b3ea373

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/chat-input-notifications

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@radroid
radroid merged commit 761b46c into main Jul 26, 2026
1 check passed
radroid added a commit that referenced this pull request Jul 27, 2026
… push)

Closes the accepted v1 gap from #14: notifications previously only fired while
a T3 tab was open. This delivers a push when the tab is fully closed.

Client (apps/web):
- PWA manifest + icons + installability (required for iOS Safari Web Push).
- Push-only service worker (no fetch handler, so no offline/stale-asset risk):
  shows a notification on push, focuses/opens the waiting thread on click, and
  suppresses when any T3 tab is open so the in-page NotificationCoordinator
  stays the single source while a tab is alive.
- PushSubscriptionManager keeps the subscription in sync with the existing
  notifyOnNeedsInput setting + permission (reacts to a post-mount grant via the
  Permissions API). Pure, tested VAPID/subscription helpers.

Server (apps/server/src/t3x/webPush — fork-seam clean, no upstream edits):
- Reactor taps OrchestrationEngine.streamDomainEvents, recomputes the awareness
  phase with the shared projectThreadAwareness, edge-detects the same
  transitions the web client does (waiting_for_input/approval; running->
  completed), and sends Web Push to registered subscriptions, pruning expired
  (404/410) ones.
- JSON subscription store in the state dir; VAPID keypair via ServerSecretStore
  (T3X_VAPID_* env override supported); raw
  /api/t3x/push/{subscribe,unsubscribe,vapid-public-key} routes mounted through
  the existing T3xRoutesLive seam.

Verified: apps/web + apps/server typecheck clean (0 errors); web + server unit
tests pass; web-push loads at runtime; server+bin construction tests (277) pass.
Browser end-to-end (grant permission, close tab, receive push) is manual QA.

Closes #19

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
radroid added a commit that referenced this pull request Jul 28, 2026
… push)

Closes the accepted v1 gap from #14: notifications previously only fired while
a T3 tab was open. This delivers a push when the tab is fully closed.

Client (apps/web):
- PWA manifest + icons + installability (required for iOS Safari Web Push).
- Push-only service worker (no fetch handler, so no offline/stale-asset risk):
  shows a notification on push, focuses/opens the waiting thread on click, and
  suppresses when any T3 tab is open so the in-page NotificationCoordinator
  stays the single source while a tab is alive.
- PushSubscriptionManager keeps the subscription in sync with the existing
  notifyOnNeedsInput setting + permission (reacts to a post-mount grant via the
  Permissions API). Pure, tested VAPID/subscription helpers.

Server (apps/server/src/t3x/webPush — fork-seam clean, no upstream edits):
- Reactor taps OrchestrationEngine.streamDomainEvents, recomputes the awareness
  phase with the shared projectThreadAwareness, edge-detects the same
  transitions the web client does (waiting_for_input/approval; running->
  completed), and sends Web Push to registered subscriptions, pruning expired
  (404/410) ones.
- JSON subscription store in the state dir; VAPID keypair via ServerSecretStore
  (T3X_VAPID_* env override supported); raw
  /api/t3x/push/{subscribe,unsubscribe,vapid-public-key} routes mounted through
  the existing T3xRoutesLive seam.

Verified: apps/web + apps/server typecheck clean (0 errors); web + server unit
tests pass; web-push loads at runtime; server+bin construction tests (277) pass.
Browser end-to-end (grant permission, close tab, receive push) is manual QA.

Closes #19

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@radroid
radroid deleted the t3code/chat-input-notifications branch July 30, 2026 14:23
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.

Notify the user when a chat needs input (desktop + web/mobile browser)

1 participant