Skip to content

Desktop pop-up notifications (Jelly-style toast)#31

Merged
TripleU613 merged 12 commits into
JTech-Forums:mainfrom
Shalom-Karr:desktop-popup-notifications
Jul 1, 2026
Merged

Desktop pop-up notifications (Jelly-style toast)#31
TripleU613 merged 12 commits into
JTech-Forums:mainfrom
Shalom-Karr:desktop-popup-notifications

Conversation

@Shalom-Karr

Copy link
Copy Markdown
Member

Desktop pop-up notifications (Jelly-style toast)

A new popup_notifications sub-plugin: an in-browser toast card that appears in the top-right corner (just below the header search) when a new notification arrives, modelled on the Jelly macOS notifier's look and delivery.

Behavior

  • Purely additive. It subscribes to the same /notification/:user_id MessageBus channel that already drives the bell counter and the notifications dropdown, and renders a card — the bell, the dropdown, and read-state are all untouched. Turning it off simply stops the card.
  • Desktop only. Never mounts on mobile (site.mobileView).
  • Opt-in per user, OFF by default. Each user turns it on via a Desktop Pop Up Notifications On/Off dropdown on their account page (/u/:username/preferences/account), stored in the jtech_popup_notifications_enabled user custom field. popup_notifications_default_enabled (default false) controls the default for users who haven't chosen, so enabling the plugin never surprises the whole forum.
  • Card layout: the acting user's name on top, their avatar on the left, the topic title in bold, then a short preview of their message (fetched from the source post).
  • Interaction: clicking the card routes to the post (same as clicking the row in the dropdown); clicking anywhere else — or waiting popup_notifications_timeout_seconds (default 20) — dismisses it.

Every code path is gated (popup_notifications_enabled + per-user pref + desktop) and wrapped so a malformed payload can never break the page.

Settings

Setting Default Purpose
popup_notifications_enabled true Master switch. Off ⇒ no card for anyone, per-user preference hidden.
popup_notifications_default_enabled false Default for users who haven't set the account-page preference.
popup_notifications_timeout_seconds 20 Seconds the card stays before auto-dismissing.

Tests

  • spec/system/popup_notifications_spec.rb — proves the additive contract end-to-end with a real reply: OFF ⇒ the reply still creates the normal replied notification and NO toast; ON ⇒ same notification plus the toast, click-to-route, and click-outside dismiss.
  • spec/system/popup_notifications_screenshots_spec.rb — a 15-shot gallery across both surfaces: the account-page control (default off / dropdown open / set on / set off), the admin master switch, the control hidden when the master is off, a toast per notification type (reply, mention, quote, PM, like), content-shape variety (long-title ellipsis, long-message clamp, bell-icon fallback), and the off state.
  • spec/requests/popup_notifications_pref_spec.rb — the preference is editable, off by default, and default-aware on the current-user serializer.

Linting (stree, prettier, eslint, stylelint, ember-template-lint) all pass locally.

🤖 Generated with Claude Code

Shalom-Karr and others added 3 commits July 1, 2026 13:45
New `popup_notifications` sub-plugin: an in-browser toast card that appears
top-right (below the header search) when a new notification arrives,
modelled on the Jelly macOS notifier's look and delivery.

Purely additive — it subscribes to the same `/notification/:user_id`
MessageBus channel that already drives the bell counter and the
notifications dropdown, and renders a card; the bell, dropdown, and
read-state are untouched. Turning it off just stops the card.

- Desktop only (never mounts on `site.mobileView`).
- Opt-in per user, OFF by default: a "Desktop Pop Up Notifications" On/Off
  dropdown on the account page, stored in the `jtech_popup_notifications_enabled`
  user custom field; `popup_notifications_default_enabled` (default false)
  controls the default for users who haven't chosen.
- Card layout: acting user's name on top, avatar on the left, topic title
  bold, then a preview of their message (fetched from the source post).
- Click the card → route to the post (same as the dropdown row); click
  anywhere else, or wait `popup_notifications_timeout_seconds`, to dismiss.

Backend is thin: registers the editable boolean custom field and exposes
the effective (default-aware) value on the current-user serializer.

Tests:
- spec/system/popup_notifications_spec.rb — screenshots both states and
  proves the additive contract: OFF ⇒ the reply still creates the normal
  `replied` notification and NO toast; ON ⇒ same notification PLUS the toast,
  click-to-route, and click-outside dismiss.
- spec/requests/popup_notifications_pref_spec.rb — the preference is
  editable, off by default, and default-aware on the serializer.

Linting: stree, prettier, eslint, stylelint, ember-template-lint all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
- Add spec/system/popup_notifications_screenshots_spec.rb: 15 screenshots
  across both surfaces — the account-page preference control (default off,
  dropdown open, set on, set off), the admin master switch, the control
  hidden when the master is off, a toast per notification type (reply,
  mention, quote, PM, like), content-shape variety (long title ellipsis,
  long message clamp, bell-icon fallback), and the off state (no toast).
  Toast shots publish crafted notifications on /notification/:id for exact,
  fast visual states; the real end-to-end path stays covered by the
  behavioral spec.
- Drop redundant `type: :system` metadata (Discourse/NoSystemSpecMetadata).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
@Shalom-Karr Shalom-Karr requested a review from TripleU613 as a code owner July 1, 2026 17:55
Shalom-Karr and others added 6 commits July 1, 2026 14:02
The Feature Screenshots workflow runs a fixed spec list and uploads the
resulting tmp/capybara PNGs as an artifact. Add the new
popup_notifications_screenshots_spec.rb so its 15 shots are captured and
published for review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The On/Off dropdown was mounted on the `user-custom-preferences` outlet,
which lives on the PROFILE page — not the account page — so it never
rendered (the 01–04 screenshot example failed on a missing selector).

Move it to `user-preferences-account` (confirmed present on the account
page in Discourse 2026.7) and save immediately on change via
PUT /u/:username.json instead of relying on the account form's Save
button, which does not persist arbitrary custom fields. The value is
mirrored onto the current user for live toast gating and reverted on
error. This save path is the one exercised by
spec/requests/popup_notifications_pref_spec.rb.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The account-page dropdown rendered "[en.jtech_popup_notifications.preference.on]"
instead of "On" — YAML parses bare `on:`/`off:` keys as booleans, so those
two i18n keys never existed (title/instructions resolved fine). Quote the
keys. Caught by the 01 screenshot.

Also switch the screenshot spec's row selection to literal "On"/"Off"
instead of I18n.t, which is simpler and avoids the same trap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
- Heading line is now "Name — Action" (e.g. "pat — Liked your post"),
  driven by a per-type action label (config/locales client action.*).
- Add a small type-icon badge on the avatar's corner; postless
  notifications render the type icon on its own instead of an avatar.
- Decode the plugin's own `custom` notifications so they pop too and read
  correctly: moderator whispers (eye), flag notes (flag), and
  queued/pending-post approvals/rejections (check / xmark), plus the other
  mod-note kinds. These already publish on /notification/:id
  (staff_notifier + the whisper dedupe job), so the toast picks them up.
- Register the badge SVG icons.
- Screenshots: type shots now show the heading + badge; add whisper, flag,
  and pending-approved shots (16–18). Behavioral spec asserts the new
  name/action classes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The behavioral spec relied on PostCreator triggering a synchronous
`replied` notification, but PostAlerter runs async in system tests — so no
notification fired and the toast never appeared. The screenshot spec pushed
several notifications per example; under the parallel runner only the first
per page delivered.

Both now use the reliable pattern: one crafted MessageBus publish on
/notification/:id per FRESH page load (the same delivery core uses). The
client registers its subscription position at page load, before the publish,
so the message is always delivered. Behavioral spec proves off→no toast,
on→toast (name/action/title) + click-to-route + click-outside dismiss;
screenshot gallery grows to 18 (adds whisper/flag/pending).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
After a warm page load, #post_1 can render before the browser's MessageBus
poll re-establishes its subscription, so a lone publish lands before the
client is listening and is missed — which dropped the 2nd+ shot in each
multi-shot example under turbo. Re-publish (fresh id) until the toast (or a
specific type icon) appears, in both the screenshot and behavioral specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
@Shalom-Karr

Copy link
Copy Markdown
Member Author
image

Shalom-Karr and others added 3 commits July 1, 2026 15:13
Concurrent notifications now STACK one below another (newest on top, just
below the header search), up to 3 at once; a 4th drops the oldest (bottom)
card. Each card keeps its own auto-dismiss timer; clicking a card opens it
and clicking anywhere else dismisses them all. The toast host became a fixed
column container; cards are normal children.

Tests:
- popup_notifications_stacking_screenshots_spec.rb — 25 shots across
  single/double/triple stacks, type mixes, content shapes, and the
  overflow-replaces-oldest behavior.
- popup_notifications_click_through_spec.rb — 30 examples: a notification
  about a post in a DIFFERENT topic pops the toast, and clicking it opens
  that topic at the post and dismisses the card (5 types × 6 target topics;
  batch 1 also screenshots before/after).
- Both wired into the Feature Screenshots workflow.

Reliability: each stack build primes the MessageBus subscription then
publishes one card at a time waiting for the exact count; click-through
retries the publish until the card lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
Two fixes for the failing system_tests job:

- The `liked` click-through failed on every batch because its target title
  "Target 1 liked" is 14 chars, under Discourse's 15-char minimum, so the
  topic fabrication raised. Give the target topics longer titles.

- The 55 browser-based gallery examples (18 single + 25 stacking + 30
  click-through) overloaded the shared, parallel system_tests job and timed
  out. Gate all three gallery specs behind JTECH_SCREENSHOT_GALLERY (set only
  in the Feature Screenshots workflow) so they generate screenshots there and
  skip in system_tests. Core behavior — off/on, click-to-open, dismiss —
  stays gated by the lean popup_notifications_spec.rb.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The stacking screenshots run only in the single-process Feature Screenshots
workflow (no per-example retry), where one flaky capture aborted the rest of
a grouped example and dropped later shots. Split the 25 stacks into 25
independent examples (fresh session each) so a flake isolates to one shot and
the gallery captures the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
@Shalom-Karr

Copy link
Copy Markdown
Member Author
popup_notifications_clickthrough_quoted_01_toast_on_home

Clicking on this opens this 👇

popup_notifications_clickthrough_quoted_02_opened_target

@Shalom-Karr

Copy link
Copy Markdown
Member Author
popup_notifications_stack_14_mention_quote_reply

@TripleU613 TripleU613 merged commit 277e274 into JTech-Forums:main Jul 1, 2026
6 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.

2 participants