From 173d996de3ee4e2ad1e73736c52773691443429a Mon Sep 17 00:00:00 2001 From: Coracle-Bot Date: Sat, 26 Sep 2026 03:45:58 +0000 Subject: [PATCH] Only raise notifications for new messages (#663) --- .../AndroidPushFallbackWorker.kt | 4 +- docs/feature_matrix.html | 2 +- e2e/USER_STORIES.md | 12 ++++ e2e/specs/notifications.spec.ts | 58 +++++++++++++++++++ e2e/specs/settings.spec.ts | 4 +- .../components/SpaceJoinNotifications.svelte | 2 +- src/app/push/adapters/common.ts | 18 +++--- src/app/push/adapters/web.ts | 2 +- src/routes/settings/alerts/+page.svelte | 6 +- 9 files changed, 86 insertions(+), 22 deletions(-) diff --git a/android/app/src/main/java/social/flotilla/notifications/AndroidPushFallbackWorker.kt b/android/app/src/main/java/social/flotilla/notifications/AndroidPushFallbackWorker.kt index 0bd79e4f..420cd8bb 100644 --- a/android/app/src/main/java/social/flotilla/notifications/AndroidPushFallbackWorker.kt +++ b/android/app/src/main/java/social/flotilla/notifications/AndroidPushFallbackWorker.kt @@ -200,12 +200,10 @@ class AndroidPushFallbackWorker(context: Context, params: WorkerParameters) : Wo PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) - val body = "New activity" - val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(android.R.drawable.stat_notify_chat) .setContentTitle("Flotilla") - .setContentText(body) + .setContentText("New message") .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) diff --git a/docs/feature_matrix.html b/docs/feature_matrix.html index e18b063c..0aa9f2fa 100644 --- a/docs/feature_matrix.html +++ b/docs/feature_matrix.html @@ -156,7 +156,7 @@ the story catalog in e2e/USER_STORIES.md.

Push notifications Implemented -app/push/, settings/alerts; web, FCM/APNs and Android fallback. US-086, US-120 +app/push/, settings/alerts; web, FCM/APNs and Android fallback. US-086, US-120, US-136 Threads diff --git a/e2e/USER_STORIES.md b/e2e/USER_STORIES.md index 1075ec44..d546033f 100644 --- a/e2e/USER_STORIES.md +++ b/e2e/USER_STORIES.md @@ -1675,6 +1675,18 @@ Acceptance: - Its body is the words bob wrote rather than the quote his reply is prepended with, and a url in it is named by its host instead of spelled out. +### US-136 — Be notified about messages and nothing else + +As alice, I want notifications to be about what people say to me, so that a +space with busy boards does not buzz my phone all day. + +Acceptance: + +- With push notifications on and the tab in the background, a message from bob + in a room alice is in raises one. +- A thread bob starts in the same room raises none, however much of it reaches + her client. + ### US-105 — Land on the home page As a new user, I want the home page to route me somewhere useful, so that I'm diff --git a/e2e/specs/notifications.spec.ts b/e2e/specs/notifications.spec.ts index d2629232..557cf5ec 100644 --- a/e2e/specs/notifications.spec.ts +++ b/e2e/specs/notifications.spec.ts @@ -9,6 +9,8 @@ import { expect, message, messageActions, + modalForm, + noteEditor, openRoomDetail, pageBar, pathPattern, @@ -867,3 +869,59 @@ test("US-120 read what a notification says", async ({seed, as}) => { body: "sunday, the notice is at a link to harbor.example", }) }) + +test("US-136 hear about a new message and not about a new thread", async ({seed, as}) => { + const scenario = await seed(({relay, user}) => { + const space = relay("space") + + space.room("general", {name: "General"}) + space.join(user.alice, "general") + space.join(user.bob, "general") + space.profile(user.alice, {name: "Alice Anchor"}) + space.profile(user.bob, {name: "Bob Barnacle"}) + }) + + const space = scenario.space("space") + + // The tab has to be in the background before a notification is raised at all. + const alice = await as(users.alice, "/settings/alerts", { + context: {permissions: ["notifications"]}, + }) + + await settingRow(alice, "Enable push notifications").getByRole("checkbox").check() + await alice.getByRole("button", {name: "Save Changes"}).click() + + await expect(alice.getByRole("alert")).toContainText("Your settings have been saved!") + + await spaceNavItem(alice, space.name).click() + await roomLink(alice, "General").click() + + await expect(alice).toHaveURL(pathPattern(roomPath(space.url, "general"))) + + const notifications = await captureNotifications(alice) + + const bob = await as(users.bob, `${spacePath(space.url)}/threads`) + const board = bob + .locator("section") + .filter({has: bob.getByRole("heading", {name: "General", exact: true})}) + + await board.getByRole("button", {name: "Create", exact: true}).click() + + const threadForm = modalForm(bob, "Create a Thread") + + await threadForm.getByPlaceholder("What is this thread about?").fill("the dock is closing") + await noteEditor(threadForm).pressSequentially("sunday, apparently") + await threadForm.getByRole("button", {name: "Create Thread"}).click() + + await expect(board.getByRole("row").filter({hasText: "the dock is closing"})).toBeVisible() + + // Alice holds the thread before bob says anything, so a notification for it would be here by now. + await contentNavItem(alice, "Threads").click() + await expect(alice.getByRole("row").filter({hasText: "the dock is closing"})).toBeVisible() + + await postTo(bob, "General", "when does the dock close?") + + await expect + .poll(() => notifications) + .toEqual([{title: "New message", body: "when does the dock close?"}]) +}) diff --git a/e2e/specs/settings.spec.ts b/e2e/specs/settings.spec.ts index c80a2418..59b0e045 100644 --- a/e2e/specs/settings.spec.ts +++ b/e2e/specs/settings.spec.ts @@ -65,7 +65,7 @@ test("US-086 configure alerts", async ({seed, as}) => { const page = await as(users.alice, "/settings/alerts") - const sound = settingToggle(page, "Play sound for new activity") + const sound = settingToggle(page, "Play sound for new messages") const alertTypes = page.locator("div.card").filter({has: page.getByText("Alert Types")}) await expect(sound).toBeChecked() @@ -98,7 +98,7 @@ test("US-086 configure alerts", async ({seed, as}) => { await waitForToastToClear(page) await page.reload() - await expect(settingToggle(page, "Play sound for new activity")).not.toBeChecked() + await expect(settingToggle(page, "Play sound for new messages")).not.toBeChecked() await expect(reminders).toHaveCount(2) await expect(reminders.nth(1).locator("input")).toHaveValue("1440") }) diff --git a/src/app/components/SpaceJoinNotifications.svelte b/src/app/components/SpaceJoinNotifications.svelte index 055cdd88..1e171942 100644 --- a/src/app/components/SpaceJoinNotifications.svelte +++ b/src/app/components/SpaceJoinNotifications.svelte @@ -11,7 +11,7 @@
Enable notifications for this space

- Get notified about new activity in this space. You can change this later in settings. + Get notified about new messages in this space. You can change this later in settings.

diff --git a/src/app/push/adapters/common.ts b/src/app/push/adapters/common.ts index 1d618941..b33d407e 100644 --- a/src/app/push/adapters/common.ts +++ b/src/app/push/adapters/common.ts @@ -1,7 +1,7 @@ import {writable} from "svelte/store" import type {Subscriber, Unsubscriber} from "svelte/store" import {Capacitor} from "@capacitor/core" -import {assoc, call, ms, now, on, parseJson, poll, spec, throttle, uniq} from "@welshman/lib" +import {call, ms, now, on, parseJson, poll, spec, throttle, uniq} from "@welshman/lib" import {LOCAL_RELAY_URL} from "@welshman/net" import type {RepositoryUpdate} from "@welshman/net" import { @@ -19,7 +19,7 @@ import {merged, withGetter} from "@welshman/store" import {User} from "@welshman/app" import {navigate} from "@app/modal" import {app, messagingRelayLists, network, roomLists} from "@app/core" -import {DM_KINDS, CONTENT_KINDS, makeCommentFilter} from "@app/content" +import {DM_KINDS} from "@app/content" import {getMutedRooms, notificationSettings, shouldNotify, userSettingsValues} from "@app/settings" import {makeEventPath, goToChat, goToSpace} from "@app/routes" @@ -47,10 +47,6 @@ export interface IPushAdapter { } export const onNotification = call(() => { - const allFilters = [ - {kinds: [MESSAGE, ...CONTENT_KINDS, ...DM_KINDS]}, - makeCommentFilter(CONTENT_KINDS), - ] const subscribers: Subscriber[] = [] let unsubscribe: Unsubscriber | undefined @@ -62,7 +58,7 @@ export const onNotification = call(() => { let unsubscribeRepository: Unsubscriber | undefined const unsubscribeApp = app.subscribe($app => { unsubscribeRepository?.() - const filters = allFilters.map(assoc("since", now())) + const filters = [{kinds: [MESSAGE, ...DM_KINDS], since: now()}] unsubscribeRepository = on($app.repository, "update", ({added}: RepositoryUpdate) => { const $pubkey = $app.user?.pubkey @@ -163,7 +159,7 @@ export const syncRelaySubscriptions = ( userSettingsValues, ]).subscribe( throttle(3000, ([$spaceUrls, {spaces, mentions}, $settings]) => { - const baseFilters = [{kinds: [MESSAGE, ...CONTENT_KINDS]}, makeCommentFilter(CONTENT_KINDS)] + const baseFilter = {kinds: [MESSAGE]} for (const url of $spaceUrls) { const {notify = true, exceptions = []} = $settings.alerts.find(spec({url})) || {} @@ -178,18 +174,18 @@ export const syncRelaySubscriptions = ( if (skipped.length > 0) { ignore.push({"#h": skipped}) } - filters.push(...baseFilters) + filters.push(baseFilter) } else { const included = exceptions.filter(h => !muted.includes(h)) if (included.length > 0) { - filters.push(...baseFilters.map(f => ({...f, "#h": included}))) + filters.push({...baseFilter, "#h": included}) } } } if (mentions) { - filters.push(...baseFilters.map(f => ({...f, "#p": [$pubkey]}))) + filters.push({...baseFilter, "#p": [$pubkey]}) } sync(url, "spaces", filters, ignore) diff --git a/src/app/push/adapters/web.ts b/src/app/push/adapters/web.ts index 5c60fee2..d935aa72 100644 --- a/src/app/push/adapters/web.ts +++ b/src/app/push/adapters/web.ts @@ -68,7 +68,7 @@ export class WebNotifications implements IPushAdapter { preview || "Someone tagged you in a message.", ) } else if (spaces) { - this._notify(event, "New activity", preview || "Someone posted a new message.") + this._notify(event, "New message", preview || "Someone posted a new message.") } } }) diff --git a/src/routes/settings/alerts/+page.svelte b/src/routes/settings/alerts/+page.svelte index 9463ccf4..ef67b007 100644 --- a/src/routes/settings/alerts/+page.svelte +++ b/src/routes/settings/alerts/+page.svelte @@ -108,7 +108,7 @@ {#if !Capacitor.isNativePlatform()} {#snippet label()} -

Play sound for new activity

+

Play sound for new messages

{/snippet} {#snippet input()} @@ -131,7 +131,7 @@ Alert Types {#snippet label()} -

Notify me about new activity

+

Notify me about new messages in my spaces

{/snippet} {#snippet input()} @@ -147,7 +147,7 @@
{#snippet label()} -

Notify me about new messages

+

Notify me about new direct messages

{/snippet} {#snippet input()}