Skip to content

[judy] feat(notify): OS notifications for new unread threads#60

Merged
SymbolStar merged 1 commit into
mainfrom
judy/os-notifications
Jun 4, 2026
Merged

[judy] feat(notify): OS notifications for new unread threads#60
SymbolStar merged 1 commit into
mainfrom
judy/os-notifications

Conversation

@SymbolStar

Copy link
Copy Markdown
Owner

Thread: th_19e6e6a8a5a_2979da

What

PWA shell (PR #46) made OpenForge feel like an app; this hooks up the matching capability — when a thread you haven't read gets a new post, you get a real macOS Notification banner (so you don't have to keep peeking at the browser tab for the red dot).

How it works

Entirely client-side. Hooked into the existing updateUnreadTitle() pass that already runs on every poll / SSE refresh:

  1. _collectUnreadIndex() walks loaded squads and returns Map<thread_id, {thread, squad}> of currently-unread threads.
  2. Diff the new id set against _prevUnreadIds (snapshot from last tick). Anything in new \ old is a brand-new unread → ping.
  3. Each ping is a new Notification(title, { tag: 'openforge:' + thread_id }) — tag de-dupes if the same thread bumps twice in a tick.
  4. Click handler does window.focus() + selectThread(thread_id) (and flips currentSquadId so the thread is actually visible).

Guards (each independently can block a banner)

  • Permission not granted (Notification.permission !== 'granted')
  • User hasn't toggled it on (_notifyPrefs.enabled)
  • Tab visible (document.visibilityState === 'visible') — red dot is enough when you're already looking
  • Thread is the currently open one
  • First pass after load: only seeds _prevUnreadIds, never notifies (avoid blasting the full backlog on app open)

Settings UI

New fieldset in the Settings modal: 系统通知 with a checkbox + status label (未授权 / 已授权但未启用 / 已启用 / 浏览器拒绝了权限 / 不支持). Permission request only fires when the user toggles on — never on page load. Stored in localStorage:openforge.notifyPrefs.v1 (separate from avatar prefs).

Toggling on also fires a one-shot test banner ("系统通知已启用") so the user sees what to expect.

Files

  • web/app.js — notify infra (~110 lines), settings wiring (~50 lines)
  • web/index.html — new fieldset in settings modal
  • web/style.css — 6 lines for .notify-row

Test plan

  1. forge update to pull
  2. Open OpenForge, Settings → 系统通知 → toggle on → grant permission → see test banner
  3. Switch to another app (tab loses focus), have another agent post to a thread you're not viewing → banner shows up
  4. Click banner → window focuses, thread opens

Out of scope

  • Sound customization / per-squad mute / quiet hours → if scott actually wants those, separate PR
  • Service-worker push (for when the tab is fully closed) → Web Notifications still need an alive tab; doing real background push needs a showNotification from sw.js + push subscription. Phase 2 if needed.

cc @scott

…posts

Trigger a Web Notification when a thread transitions read \u2192 unread,
gated on:
  - user opted in via Settings \u2192 \u7cfb\u7edf\u901a\u77e5 toggle
  - browser Notification permission granted
  - tab hidden (visible tabs already show red dot/badge)
  - thread is not the currently-open one

Implementation lives entirely client-side (no backend churn). Hooks into
the existing updateUnreadTitle() pass: every refresh diffs the current
unread-thread-id set against the previous snapshot, fires one banner per
newly-unread thread. Notification tag = thread_id (collapses repeats).
Click \u2192 window.focus() + selectThread().

Settings:
  - new fieldset \u201c\u7cfb\u7edf\u901a\u77e5\u201d with a checkbox + status hint
  - permission request happens on toggle-on, not on page load
  - separate localStorage key (openforge.notifyPrefs.v1) so it's
    portable away from the avatar prefs

First-load guard: _prevUnreadIds starts as null \u2192 first pass seeds
only, so opening the app with an existing backlog doesn't flood you
with notifications.
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

🤖 bot-review (comment-only · phase 1)

Diff: 3 files changed, 181 insertions(+) @ 2672641

Red-line checks:

  • ✅ A-7.5: no new 'forbidden' code in xiaof

Phase 1: this bot leaves comments only. Auto-approve will be enabled per-path after 1–2 weeks of clean runs. Promotion plan: judy PR #42 follow-up.

@SymbolStar SymbolStar merged commit d4e7f8c into main Jun 4, 2026
7 checks passed
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