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-318Done

5.7.6 Per-user notification preferences — `NotificationPreference` model + the settings page (Jira shape); the channel gate BOTH the 5.7.3 in-app job AND the DONE 5.1.6 email job honour

Estimate: 38m · Depends on: 5.7.2, 5.7.1

The per-user notification preferences (the Jira personal-notification-settings shape) + the channel gate that makes them real. Design-gated (5.7.1, the preferences panel) and built on the 5.7.2 foundation. Full 4-layer for the model + settings surface.

NotificationPreference model (prisma/schema.prisma + migration, two-sided FK): { id, userId, eventType, channel, enabled } with @@unique([userId, eventType, channel]) — a per-user × event-type × channel (email | in_app) toggle. Absence = the documented default (direct/mention events default both channels ON; the resolver supplies the default so an unset row is not "off"). userId FK a two-sided @relation (User.notificationPreferences, onDelete Cascade). prisma migrate dev reports no drift.

The resolver — the channel gate (the load-bearing seam). lib/services/notificationPreferencesService.ts exposes isChannelEnabled(userId, eventType, channel) (and a batch variant) resolving a row-or-default. This is the SAME gate consulted by: • the 5.7.3 in-app job (the call site it stubbed with a permissive default — now backed by the real resolver), AND • the DONE 5.1.6 email job — wired here to consult isChannelEnabled(recipient, eventType, "email") before sending, so toggling email OFF actually suppresses the mail. This is a gate ADDED at 5.1.6's SEND decision, not a change to any emit site — the event still fires once; the email job just asks the resolver before dispatching (the one-emit-path invariant holds). 5.1.6 already documents the preference toggle as its seam (its description), so this is the planned wiring, not a reach into frozen code.

The settings page (app/(authed)/settings/account/_components/ — the existing personal-settings area, beside LanguageCard): the per-event-type × channel matrix of toggles from the 5.7.1 design (Mentioned / Commented / Assigned / [Watching: transitioned — disabled 5.4 seam] × Email · In-app), with the Watching-event rows drawn disabled until 5.4 lands. Toggling a cell is an inline mutation through a Server Action calling the service — the success response IS the confirmation (no whole-tree refresh; the inline-edit memory), with saving/saved/error inline states. A GET/PUT /api/notification-preferences route pair (HTTP-only) backs it.

Acceptance criteria

  • NotificationPreference (@@unique([userId, eventType, channel]), two-sided @relation, Cascade) + migration land; prisma migrate dev reports no drift; an unset preference resolves to the documented default (direct/mention ON), not "off".
  • notificationPreferencesService.isChannelEnabled is the single resolver; the 5.7.3 in-app job consults it (in_app) AND the DONE 5.1.6 email job is wired to consult it (email) at its SEND decision — toggling email off stops the mail, toggling in-app off stops the bell row, with NO change to any emit site (the event still fires once).
  • The settings page renders the event × channel matrix on /settings/account with the Watching-event rows disabled (the 5.4 seam); toggling persists through a Server Action whose success response updates the cell (no router.refresh fan-out); saving/saved/ error states match the 5.7.1 design.
  • Routes are HTTP-only; pnpm test:coverage holds the gate on the new model/service/ page code.

Context refs

  • 5.7.2 (the Notification model + the type axis the preference keys on); 5.7.3 (the in-app call site this backs); 5.7.1 (the preferences-panel design)
  • lib/jobs/definitions/mentionNotify.ts (5.1.6, DONE) — the email job whose SEND decision this gates (the documented preference seam in its 5.1.6 card)
  • app/(authed)/settings/account/page.tsx + _components/LanguageCard.tsx — the existing personal-settings area + card grammar to extend; components/ui/Switch + ContentSectionCard
  • motir-core/CLAUDE.md — 4-layer; the inline-edit-no-whole-tree-refresh memory (the toggle success IS confirmation); the Jira Personal settings → Notification settings mirror