pearcord-notifications
Local notification inbox, desktop prefs, and cross-device settings gossip.
Phase 675 (v0.8.650): Extended NOTIFICATION_KIND (poll, scheduled, voice_missed, stage, boost, friend_online), roleMentionUserIds in evaluateInbound, restoreReadStates, platform notifications-activity.js mixin (fanout rate limit, gossip dedupe, partition heal). Bundle: npm run test:ci-phase675.
Phase 670 (v0.8.645): notifyOnSticker desktop pref (default true). Bundle: npm run test:ci-phase670.
Phase 669 (v0.8.644): notifyOnReaction desktop pref (default true) — DM reaction notifications respect user opt-out. Bundle: npm run test:ci-phase669.
Platform integration (v0.8.461)
pearcord-platform#markAllNotificationsRead logs inbox.mark-all spans (unreadBefore, remainingUnread). UI inbox compositor: syncInboxPanelsDuringGuildLoading. Verification: npm run test:phase498-inbox.
Mission
Evaluate inbound messages against user prefs (mentions, DMs, replies, all-messages, announcements), write inbox rows, expose unread counts and guild badge aggregation helpers, and sync notification/desktop prefs across devices on settingsTopic via Protomux (pearcord-settings-v1). Does not render OS notifications itself — platform/companion emits UI events.
When to use / not
Use when:
- You need inbox CRUD or prefs merge logic in a companion process.
- You are testing
SETTINGS_UPDATEmesh convergence without Pear UI.
Do not use when:
- You need push notification gateways (APNs/FCM) — out of scope; desktop flag is pref only.
- You need email digests — use
pearcord-digest-relay-plugin.
Public API
| Export | Role |
|---|---|
PearcordNotifications |
EventEmitter service |
NOTIFICATION_KIND |
mention, dm, reply, message, announcement |
DEFAULT_PREFS |
Desktop + channel mute defaults + channelNotificationLevels + channelMuteUntil |
getChannelNotificationLevel |
Resolve all / mentions / nothing for a channel |
setChannelMuteDuration(channelId, durationMs) |
Timed mute (0 clears); updates channelMuteUntil + mutedChannelIds |
pruneExpiredChannelMutes / isChannelTimedMuted |
Helpers for expiry |
INBOX_COLLECTION / PREFS_COLLECTION |
Store keys |
ready, setUserId |
Init |
getPrefs / setPrefs(patch, { gossip }) |
Prefs + optional mesh fanout |
evaluateInbound({ message, mode, channelType, parentChannelName, ... }) |
Returns inbox row or null; thread place includes parent channel name |
push(record) |
Insert + emit notification |
listRecent, countUnread, markRead, markAllRead, clearForChannel |
Inbox |
aggregateGuildBadges(channelMeta) |
Server rail badges |
joinMesh(swarm, { deviceId, manageListeners }) |
Settings topic |
ingestSettingsUpdate, simulateGossip |
Multi-device prefs |
./mesh |
attachSettingsMesh, broadcastSettingsGossip |
v0.8.441 (Phase 478): Platform span metadata extended via pearcord-platform wrappers (wasUnread, unreadBefore, clearedInboxCount, hasMessageId). UI compositor + clipboard meta. Bundle: test:phase478-notifications (app repo).
Platform (v0.8.432) extends spans: markNotificationRead kind/read; markAllNotificationsRead inboxCount; openNotificationTarget messageId; markChannelRead threadRead/isDm. UI uses syncNotificationPanelsDuringGuildLoading + composer notif hint. Bundle: test:phase469-notifications-inbox; smokes: test:platform-notification-span-metadata-extend.
Platform (v0.8.416) extends openNotificationTarget / markAllNotificationsRead / markChannelRead spans with guildId on end. UI inbox panel (Phase 453) adds All/Unread/Mentions filter chips and list keyboard roving; guild-loading disables chips and rows via syncNotifPanelDuringGuildLoading. Bundle: test:phase453-notifications-inbox; smoke: test:platform-notification-span-guild-id.
Platform (v0.8.407) wraps markRead as markNotificationRead with notification.markRead span + error; logs notification.markAll error and notification.open error on inbox navigation failures. Dev-log notification-errors filter matches notification.markRead|markAll|open error lines (test:dev-log-notification-errors-bundle). Phase 444 bundle: test:phase444-notifications-inbox.
P2P surface
| Piece | Detail |
|---|---|
| Topic | settingsTopic(userId) |
| Protocol | pearcord-settings-v1 on shared settings swarm |
| RPC | SETTINGS_UPDATE (prefs blob + updatedAt LWW) |
Platform may share one Hyperswarm between notifications, user settings, and profile cosmetics with multiplexed wire attach.
Storage
| Collection | Path |
|---|---|
@pearcord/notification-inbox |
{storagePath}/notifications |
@pearcord/notification-prefs |
same JsonStore |
Inbox rows: title, body, place, kind, read, guildId, channelId, messageId.
Platform integration
const { PearcordNotifications } = require('pearcord-notifications')
const { attachSettingsMesh } = require('pearcord-notifications/mesh')
this.notifications = new PearcordNotifications({ userId, storagePath })
await this.notifications.ready()
await this.notifications.joinMesh(this._settingsSwarm, { deviceId })
// inbound message → evaluateInbound → push → emit to UI
UI / IPC
| IPC | Platform path |
|---|---|
get-notifications |
listRecent |
set-notification-prefs |
setPrefs |
mark-notification-read |
markRead |
| Companion jump | NOTIFICATION_KIND in smokes |
Related docs
Tests
smoke-companion-notifications.cjs,smoke-companion-notification-jump.cjssmoke-notification-jump.cjs,smoke-notification-prefs-mesh-live.cjssmoke-companion-boot.cjs— module resolves
Code example
const { PearcordNotifications, NOTIFICATION_KIND } = require('pearcord-notifications')
const n = new PearcordNotifications({ userId: 'u1', storagePath: './pearcord-storage' })
await n.ready()
await n.setPrefs({ notifyOnMention: true, notifyOnDM: true })
const row = await n.evaluateInbound({
message: { id: 'm1', channelId: 'c1', authorId: 'u2', content: '@u1 hi' },
mode: 'guild',
guildId: 'g1',
mentionedUserIds: ['u1'],
authorName: 'Peer'
})
if (row) {
row.kind === NOTIFICATION_KIND.MENTION
await n.push(row)
}
Repository
Part of Pearcord.
- Org:
pearcord - Clone:
git clone https://git.ssh.surf/pearcord/pearcord-notifications.git - Install:
npm install git+https://git.ssh.surf/pearcord/pearcord-notifications.git#main