Skip to content

feat(notifications)!: expose occurrences and queue alerts - #2020

Open
hmans wants to merge 27 commits into
hmans/notifications-thread-contextfrom
hmans/notifications-occurrence-delivery
Open

feat(notifications)!: expose occurrences and queue alerts#2020
hmans wants to merge 27 commits into
hmans/notifications-thread-contextfrom
hmans/notifications-occurrence-delivery

Conversation

@hmans

@hmans hmans commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace server-owned notification groups with exact 90-day notification occurrences and exact global/per-room unread counts
  • let the bundled client consolidate DMs, reactions, and followed activity while keeping mentions and replies occurrence-exact
  • classify each occurrence at source time as AMBIENT or IMPORTANT; reactions are neutral/ambient while current mentions, replies, DMs, and followed activity use Chatto orange
  • expose exact total and Important unread counts so bell, server, and room indicators remain present for ambient notifications but turn orange only when Important work exists
  • add optimistic exact/batch dismissal, date sections, full-sentence summaries, reaction emoji/actor consolidation, and thread-root context
  • hand eligible push work to the backed-up NOTIFICATIONS_QUEUE through events.DurableWorker, with current policy, DND, subscription, target-visibility, and materializer fences
  • update ADR-072, ADR-073, FDR-012, FDR-013, the glossary, runtime inventories, generated clients, and ConnectRPC reference docs

This PR is stacked on #2011 and should merge after it. Closes #1556.

Architecture

Notifications remain derived RUNTIME_STATE, not domain facts: this adds no notification-only events to EVT. Source-command OCC retries recompute and reconcile prepared recipients. A single causal materializer consumes existing domain events; interruptive delivery then uses the separate file-backed NOTIFICATIONS_QUEUE work queue. The queue and consumer are included in backups so accepted pending work survives the backup boundary, while a strict two-minute stream and worker age horizon prevents stale restored pushes.

The public API exposes individual occurrences. Presentation grouping belongs to clients and never changes unread counts, read state, deletion identity, jump targets, or attention level. Visual attention is deliberately independent from push delivery intensity (OFF / BADGE / ALERT), leaving room for future user-configurable attention without changing occurrence identity or delivery policy.

Attention is persisted on each occurrence at source-command preparation time. Existing rows that omit the additive field derive a conservative value from their reasons. New clients talking to an older Notifications 2.0 server treat absent Important counts as all unread being Important, preserving the previous all-orange behavior.

Frontend occurrence state has one owner and fences optimistic mutations, projection resets, authorization loss/restoration, pagination, and realtime refreshes. First-page refreshes are coalesced so adjacent invalidations do not create redundant list bursts.

API compatibility

This intentionally breaks the experimental pre-1.0 notification API at the Notifications 2.0 cutover:

  • ListNotificationGroups becomes ListNotificationOccurrences
  • DeleteNotificationGroup becomes idempotent exact/batch occurrence deletion
  • realtime notification replacement carries exact occurrences and unread-occurrence counts
  • reaction emoji and optional thread-root excerpts are exposed per occurrence

The attention additions themselves are additive: NotificationAttentionLevel, occurrence attention, exact Important unread counts, and per-room Important counts. Legacy Notifications 1.0 records are neither migrated nor read, as agreed for the 0.5.0 reset. Persisted/core protobuf evolution remains additive; the breaking surface is limited to the superseded public API and realtime projection.

Verification

  • mise test-cli
  • mise test-frontend (129 server files / 1,184 tests; 164 client files / 1,648 tests; 61 Storybook files / 184 tests; 5 performance comparison tests)
  • mise x -- pnpm --dir apps/frontend check (0 errors, 0 warnings)
  • notification Playwright suite (36/36)
  • notification queue, delivery-fence, and competing-replica mutation tests under Go's race detector
  • production push-handler tests for final policy/visibility/subscription fences, mixed-device outcomes, retries, and suppression
  • targeted ConnectAPI, realtime, authorization-boundary, OCC-retry, backup/restore, visibility-cleanup, high-cardinality grouping/deletion, attention, and concurrent optimistic-rollback tests
  • mise codegen-proto
  • changed-file Buf lint
  • persisted/core protobuf breaking check against the stack base
  • mise license-check
  • mise lint
  • Chrome DevTools verification of Important orange and ambient neutral rows/badges with a clean notification-page console
  • independent final code review: no actionable findings

The public Buf breaking check reports only the intentional group-API and realtime-field replacement described above.

Decision records

@hmans hmans added the api-breaking-change Intentional pre-1.0 public API breaking change label Aug 12, 2026
hmans added 25 commits August 12, 2026 13:10
…ccurrence-delivery

* origin/main:
  chore(main): release chatto-desktop 0.1.0-alpha.2 (#1900)
  feat(desktop): stream macOS games through LiveKit (#2024)
  feat(auth): manage member-authorized OAuth clients (#2014)
  feat(auth)!: require CIMD clients for remote access (#2013)
  feat(auth): identify OAuth clients through CIMD (#2012)
  fix(dockercompose): validate init-env arguments (#2023)

# Conflicts:
#	apps/docs-website/src/content/docs/releases/0-5-0.mdx
#	cli/internal/connectapi/api_contract_test.go
#	cli/internal/connectapi/api_test_helpers_test.go
#	cli/internal/core/core.go
#	cli/internal/http_server/cors_test.go
#	cli/internal/pb/chatto/core/v1/event.pb.go
#	docs/adr/INDEX.md
#	docs/architecture/interfaces.md
#	docs/architecture/subjects-and-events.md
…ccurrence-delivery

* origin/main:
  fix(realtime): refresh active calls after room access (#2031)
  fix(frontend): hydrate desktop server statuses on startup (#2029)
  feat(desktop): adapt game streams with simulcast (#2028)

# Conflicts:
#	docs/architecture/realtime-delivery.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-breaking-change Intentional pre-1.0 public API breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant