Skip to content

feat(notifications): add Badge and room-message delivery - #2119

Merged
hmans merged 16 commits into
mainfrom
hmans/plan-next-steps-v56
Aug 27, 2026
Merged

hmans merged 16 commits into
mainfrom
hmans/plan-next-steps-v56

Conversation

@hmans

@hmans hmans commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Why

Notification policy needs two missing controls. Users must be able to request a neutral room dot without creating a notification, and they must control how root messages in channel rooms request their attention. The Message Read Cursor must remain available for the New messages separator without forcing a room dot.

What changed

  • Add the Badge delivery mode. It creates only the existing neutral room or thread dot. It does not create a notification-list item, play a sound, send a push notification, or set an application-icon badge.
  • Add Room messages as an independent notification cause for root messages posted directly to channel rooms. Its product default is Badge. It applies at server, room-group, and channel-room scopes, but not to direct-message rooms.
  • Derive Room message, mention, and reply signals independently from the same source message. A root message such as @all @hmans can therefore apply each configured policy without collapsing the causes.
  • Select room-message recipients, message.read visibility, and effective policy at the exact source event sequence. Later visibility loss records a durable boundary so old output does not reappear after permission regain. Thread messages and direct messages keep their existing causes.
  • Reconcile message.read and message.read.interactions changes per exact notification target. A direct mention remains visible through interaction-scoped access, while output for inaccessible messages is removed.
  • Make public room-unread fields report Badge attention only. Keep the Message Read Cursor as separate state for the New messages separator. Reading a room or thread clears applicable Badge markers through the existing read-boundary flow.
  • Make posting record the same covered-read boundary as an explicit room read, so a poster's older Badge attention clears without using cursor lag as the dot source.
  • Pipeline distinct Badge marker keys with bounded concurrency and wait on one collective applied-revision barrier. Large default-Badge room fan-outs no longer serialize one broker round trip per recipient.
  • Narrow account deletion's owned-bot barrier to user events. Concurrent bot-owner reassignment can no longer make the user projection wait on an unrelated authorization-fence fact.
  • Remove the obsolete Followed rooms matrix row. Room messages at room scope provide the supported control for ordinary root-message activity. Keep the existing field 8 policy and signal contracts as deprecated compatibility slots; current code does not derive the signal.
  • Extend the notification matrix, occurrence presentation, push delivery, localisation catalogues, Storybook scenarios, generated clients, public API reference, user guide, FDR, ADR, glossary, and architecture inventories.

No notification-specific event or marker is added to EVT. Badge output uses the bounded notification_unread_marker.* runtime state, and Room message decisions derive from the existing persisted MessagePostedEvent.

Compatibility and rollout

  • Classification: additive public and persisted enum value for Badge; additive public and persisted room_messages = 10 policy field and signal variant; additive transient unread invalidation; and a behavioural change to the public has_unread fields.
  • The has_unread behavioural change is intentional: cursor lag no longer implies a dot. Clients that need message position must continue to use the Message Read Cursor. This change ships inside the coordinated 0.5 server replacement boundary.
  • Older clients use sparse field masks to leave the new Room message policy field unchanged. They can still render Badge attention through the existing has_unread fields, but they do not show a new Room message notification-list item.
  • The new matrix requires the Room messages field in each effective policy. An older server omits that field, so policy loading fails safely instead of presenting or saving an incomplete matrix. The bundled 0.5 client and server must be replaced together.
  • Older server binaries preserve unknown persisted protobuf fields and enum values. During rollback, unsupported Badge output and future Room message decisions become temporarily inactive; they cannot turn into Notification or Push notification decisions. If the upgraded server already persisted a Room message occurrence for Notification or Push notification, the older server's notification occurrence RPCs return Unimplemented until a supporting binary serves it again. The older server does not reinterpret or discard that occurrence.
  • The public and persisted field 8 Followed rooms policy and signal contracts stay wire-compatible and are marked deprecated. Stored overrides remain inert, and keeping the field numbers prevents older data or clients from being reinterpreted as another cause.
  • No data migration, new stream, new durable consumer, or new worker is required. Runtime marker keys contain internal IDs and notification-signal references. Realtime invalidations contain no message content.

Test plan

Passed locally:

  • mise codegen-proto and mise run build-api-types.
  • Targeted Buf lint for changed Chatto protobuf files.
  • Public and persisted/internal Buf breaking checks against origin/main.
  • mise test-cli, including regression coverage for retaining direct-mention Notification and Badge output through interaction-scoped read access.
  • mise lint-frontend.
  • mise test-frontend (including 71 Storybook files and 212 Storybook tests).
  • Production frontend and Storybook builds.
  • mise run test-e2e -- notification-policy.test.ts (4 passed), including Room messages Off suppressing the dot while preserving the New messages separator.
  • The bot-owner reassignment/account-deletion race test passed 20 consecutive local runs after the barrier fix.
  • mise run test-e2e -- notifications.test.ts --grep "Room message notifications" (1 passed).
  • Docs website production build.
  • mise license-check.
  • Svelte autofixer on every changed Svelte component.
  • Chrome DevTools acceptance on the real settings page in light and dark themes at desktop and narrow widths. This covered mouse and keyboard cycling, inheritance, applicability, horizontal scrolling, sticky headings, focus states, and console and network inspection. A final Storybook check confirmed the nine supported rows, current-member room columns, and no Followed rooms row.

The complete GitHub Actions suite passed on the final commit.

@hmans hmans added the api-breaking-change Intentional pre-1.0 public API breaking change label Aug 26, 2026
@hmans hmans changed the title feat(notifications): add Badge delivery mode feat(notifications): add Badge and room-message delivery Aug 26, 2026
@hmans hmans added api-breaking-change Intentional pre-1.0 public API breaking change and removed api-breaking-change Intentional pre-1.0 public API breaking change labels Aug 26, 2026
@hmans
hmans marked this pull request as draft August 26, 2026 16:45
@hmans
hmans marked this pull request as ready for review August 26, 2026 16:45
@hmans hmans mentioned this pull request Aug 27, 2026
@hmans hmans added api-breaking-change Intentional pre-1.0 public API breaking change and removed api-breaking-change Intentional pre-1.0 public API breaking change labels Aug 27, 2026
hmans added 3 commits August 27, 2026 10:20
…-v56

* origin/main:
  chore(cli): remove unused internal/service package (#2128)
  feat(bots): activate bots through notifications (#2127)
  feat(frontend): consolidate app preferences into settings (#2126)
  fix(frontend): move account settings to top (#2125)

# Conflicts:
#	cli/internal/core/messages.go
#	docs/fdr/FDR-012-notifications.md
@hmans
hmans merged commit 3dde347 into main Aug 27, 2026
16 checks passed
@hmans
hmans deleted the hmans/plan-next-steps-v56 branch August 27, 2026 08:46
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