Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-323Done

5.7.11 Gate the watcher TRANSITION email by the `transitioned · email` preference — `watcherNotificationsService` never consults the resolver

Estimate: 22m · Depends on: 5.7.6, 5.4.5

Why this exists (the seam nobody owned — notes.html mistake #40). 5.7.6 built the channel-preference resolver and wired the EMAIL gate into the 5.1.6 mention job (mentionNotificationsService.filterChannelEnabled) only. The 5.4.5 watcher email fan-out (watcherNotificationsService.fanOut) shipped earlier and was never wired — it enqueues an email.send for every viewable watcher with NO isChannelEnabled / filterChannelEnabled call. So a watcher who turns transitioned · email OFF still receives the transition email: without this fix the matrix toggle (5.7.12) would be DECORATIVE — the "worse than disabled" defect the bug card warns about.

Fix. In watcherNotificationsService.fanOut, on the TRANSITION branch, gate each watcher page through notificationPreferencesService.filterChannelEnabled(pageUserIds, 'transitioned', 'email') BEFORE enqueuing email.send — applied per page so the gate rides the existing bounded cursor walk (finding #57), one batch query per page. Keep the existing exclusions (actor; on comments, mentioned users) and the send-time view re-check unchanged. The event still fires once; the job just asks the resolver before dispatching (the one-emit-path invariant — same shape as the 5.7.6 mention-job gate). An unset preference resolves to the documented default (ON), so untouched watchers are unaffected.

Scope note (out of scope — log as a finding, do NOT absorb). The watcher COMMENT email (the kind: 'comment' branch) is ALSO ungated, but it has no clean matrix row: the matrix's commented row is the involved/mention path, not the watching path. A "watching · commented" preference is a separate design question (a new matrix row) — note it in the PR body as a finding; this subtask gates the TRANSITION branch by transitioned only.

Acceptance criteria

  • With transitioned · email set OFF, a work-item/transitioned event the watcher would otherwise receive does NOT enqueue email.send for that watcher — verified by an integration test against the real watcherNotificationsService.fanOut (mirror the existing watcher fan-out tests).
  • An unset / ON preference still delivers (default-on preserved); other recipients on the same event are unaffected; the actor/mention exclusions and the send-time view re-check are unchanged.
  • The gate is applied PER PAGE (one filterChannelEnabled batch query per watcher page) — no per-watcher N+1, no load-all (finding #57).
  • The watcher COMMENT branch is explicitly left as-is with a PR-body finding (no silent scope creep); pnpm test:coverage holds the ≥90% gate on the touched service.

Context refs

  • lib/services/watcherNotificationsService.tsfanOut (the transition branch + the paged roster walk to gate)
  • lib/services/notificationPreferencesService.tsfilterChannelEnabled (the batch email gate, already used by the mention job) / isChannelEnabled
  • lib/services/mentionNotificationsService.ts — the 5.7.6 mention-job gate this mirrors (the wiring pattern)
  • lib/notifications/preferences.ts (transitioned event-type meta + defaults)
  • notes.html mistake #40 (the planning gap this closes)