Skip to content

In-app notifications: emit events + localizable payloads - #227

Merged
martsokha merged 3 commits into
mainfrom
feat/notification-emitter
Aug 12, 2026
Merged

In-app notifications: emit events + localizable payloads#227
martsokha merged 3 commits into
mainfrom
feat/notification-emitter

Conversation

@martsokha

@martsokha martsokha commented Aug 12, 2026

Copy link
Copy Markdown
Member

Merged as commit on main (2026-08-12). Final scope: the full
NotificationEvent catalog defined; 7 of 9 events emitted end-to-end
(all but system:announcement/system:report, which have no trigger yet).
Notification rows are notify_type + typed params (no server-rendered
text); legacy title/message/related_id/related_type columns removed.

Summary

The notification read API existed, but only member:invited was ever created. This adds a NotificationEmitter that actually writes notifications, reshapes the event catalog to match the webhook vocabulary, and moves the copy to a client-localizable structured payload.

Event catalog (realigned with WebhookEvent)

NotificationEvent is now the human-facing set, named to match webhooks where they overlap:
member:invited, member:joined, connection:sync.completed, connection:sync.failed, pipeline:run.analyzed, pipeline:run.completed, pipeline:run.failed, system:announcement, system:report.
(Dropped the unused file:* events; renamed connection:synced/desyncedsync.completed/failed; added the pipeline-run trio.)

Localizable, structured payload

No server-rendered text. A notification stores notify_type (the localization key) + typed params; the response nests a payload: NotificationPayload — a notifyType-tagged enum with one named *Params struct per variant, so each type documents its params in OpenAPI and the client renders the copy.

  • Dropped title/message columns; renamed metadataparams.
  • Dropped related_id/related_type (superseded by the typed params); retired the dead length constraints, renamed metadata_sizeparams_size.

Emitter

NotificationEmitter (in ServiceState/DI, holds PgClient):

  • notify_account — targeted, honors the member's notification_events_app prefs (empty = all; the column now defaults to every event).
  • notify_account_direct — for a recipient who isn't yet a member (member:invited → the invitee).
  • notify_workspace_roles — broadcast to members of given roles, preference-filtered, in two queries total (notification_recipients_by_roles + a batch insert) regardless of recipient count.

Wired events

Event Recipient Site
member:invited invitee invite create
member:joined workspace owners + admins (excl. joiner) invite accept
pipeline:run.analyzed / failed run trigger detection worker
pipeline:run.completed run trigger redact handler
connection:sync.completed / failed connection owner sync service

All best-effort (log-and-continue). system:announcement/system:report are defined but unemitted (no trigger exists yet).

Notes

Testing

Full gate green (check / fmt / clippy / doc / test).

🤖 Generated with Claude Code

martsokha and others added 3 commits August 12, 2026 13:55
Reshape the notification catalog to the human-facing events worth
surfacing, using WebhookEvent's naming where the two channels overlap:

- drop the unused file:* events (uploaded/downloaded/verified)
- rename connection:synced/desynced -> connection:sync.completed/failed
- add pipeline:run.analyzed/completed/failed
- keep member:invited/joined and system:announcement/report

Update the enum helpers (drop is_file_event, add is_pipeline_event and the
pipeline category). Migration edited in place. This aligns the catalog so a
shared emitter can map overlapping events; emitting these events is a
follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Notifications had a read API but only member:invited was ever created.
Add a NotificationEmitter service that writes per-account notification rows
and wire it at the domain events worth surfacing.

Structured, localizable model (no server-rendered text):
- account_notifications: drop title/message, store notify_type (the
  client-side localization key) + typed params (renamed from metadata).
- Response nests a payload: NotificationPayload, a notifyType-tagged enum
  with one named *Params struct per variant, so each type documents its
  params in OpenAPI and the client renders the copy.

Emitter (in ServiceState/DI, holds PgClient):
- notify_account: honors the member's notification_events_app prefs
  (empty = all; the column now defaults to every event).
- notify_account_direct: for a recipient who is not yet a member
  (member:invited targets the invitee).

Wired events (all best-effort, log-and-continue): member:invited,
pipeline:run.analyzed/completed/failed, connection:sync.completed/failed.
member:joined and system:* remain defined but unemitted.

Migrations edited in place (pre-launch); DB reset + regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lumns

member:joined: on invite accept, notify the workspace's owners and admins
(excluding the joiner). The broadcast is two queries regardless of size:
notification_recipients_by_roles resolves role- and preference-filtered
recipients in one query, then create_account_notifications batch-inserts
their rows.

Drop legacy fields superseded by typed params:
- remove related_id/related_type (columns, index, model, emitter, invites);
  the tagged payload's params already carry the typed ids.
- retire the dead account_notifications constraints (title/message/related
  type length) and rename metadata_size -> params_size, with matching error
  mappings.

Emitted events now: member:invited, member:joined, pipeline:run.analyzed/
completed/failed, connection:sync.completed/failed. system:* remain
defined but unemitted (no trigger yet). Migrations edited in place; DB
reset + regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@martsokha martsokha added feat request for or implementation of a new feature server API handlers, middleware, auth postgres ORM, models, queries, migrations labels Aug 12, 2026
@martsokha martsokha self-assigned this Aug 12, 2026
@martsokha
martsokha merged commit 02273c6 into main Aug 12, 2026
9 checks passed
@martsokha
martsokha deleted the feat/notification-emitter branch August 12, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat request for or implementation of a new feature postgres ORM, models, queries, migrations server API handlers, middleware, auth

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant