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

5.7.3 In-app notification fan-in job — a 1.6-pipeline consumer of the SHIPPED 5.1.6 events writing `Notification` rows (actor-excluded, mention-deduped, EXTENSIBLE for 5.4/6.6)

Estimate: 35m · Depends on: 5.7.2

The event CONSUMER — the heart of "fed by a job, never a second emit path". A SECOND consumer (beside the DONE 5.1.6 email job) of the SAME channel-agnostic events, on the same 1.6 defineJob pipeline, that fans each event into Notification rows for the right recipients. It touches NO emit site and adds NO "also notify in-app" call anywhere — the emit already happened (5.1.6); 5.7.3 just subscribes.

Job (lib/jobs/definitions/notificationFanIn.ts, the 1.6 defineJob harness — mirroring mentionNotify.ts, the 5.1.6 in-production reference): consumes the shipped work-item/comment.created + work-item/mentioned events. For each event it computes the recipient set, then writes a Notification row per recipient (the 5.7.2 createMany, one tx) carrying the denormalized data payload the feed row renders (issue key + title, actor, a plain-text excerpt with mention tokens rendered as @Name — reusing the lib/mentions helper 5.1.6 already uses; no raw mention: leakage).

Recipient rules (inherited from the 5.1.6 / 5.4 contract — same semantics, in-app channel): the actor is ALWAYS excluded (never notify yourself — the 5.1.6 rule); mentions are deduped (a user mentioned twice in one comment gets ONE row; a user who is both mentioned and a watcher gets ONE — the 5.4 dedupe rule, mention wins the direct category); view access is re-validated at fan-in time (a recipient who can no longer view the issue gets no row — the 5.1.6 send-time re-check). Idempotent per (eventId × recipientUserId) via the 5.7.2 @@unique + the harness idempotency key — a replay or retry never double-writes; failures land in the DLQ per the 1.6 contract.

EXTENSIBLE — the fan-in seam (no forward dep). The handler dispatches on a small eventType → { category, recipients(event), summary(event) } registry so Story 5.4's work-item/transitioned (the watching category) and Story 6.6's work-item/created + work-item/field.changed fan in by ADDING a registry entry when those events land — with NO change to this job's core and NO dependency on 5.4/6.6 here (those stories document 5.7 as their seam). 5.7.3 ships only the entries for the SHIPPED 5.1.6 events; an unregistered event type is a clean no-op, not an error.

The preference gate is consulted here (the actual gating code is 5.7.6, which this job calls): before writing a recipient's row, check their per-event-type in_app preference; if off, no row. 5.7.3 ships the call site + a permissive default; 5.7.6 lands the NotificationPreference model + the resolver behind it.

Acceptance criteria

  • A work-item/comment.created / work-item/mentioned event fans into one Notification row per eligible recipient (the createMany batch in one tx), with the denormalized data payload the feed renders; the actor is excluded; mentions are deduped (twice-mentioned → one row; mention+watcher → one direct row).
  • View access is re-validated at fan-in (a non-viewer recipient gets no row); idempotency holds (replaying the event / retrying the job never double-writes, per the (eventId × recipient) key); failures land in the DLQ (the 1.6 / @inngest/test harness).
  • The handler is registry-driven: adding a new eventType entry is the ONLY change needed for 5.4/6.6 to fan in (asserted by a test that registers a synthetic event type and sees rows); an unregistered event type is a clean no-op. No import of, or dep on, Story 5.4 / 6.6 code.
  • The in-app preference gate is consulted per recipient via the 5.7.6 resolver (a permissive default until 5.7.6 lands); no emit site is touched and no second notify call is added beside any email send.
  • pnpm test:coverage holds the gate on the new job code.

Context refs

  • lib/jobs/definitions/mentionNotify.ts (5.1.6) — the in-production consumer this mirrors (the SECOND consumer of the same events); lib/jobs/defineJob.ts + lib/jobs/types.ts — the harness, idempotency, DLQ + the typed JobEventDataMap (consumed, NOT extended here)
  • 5.7.2 notificationRepository.createMany + the @@unique idempotency key
  • lib/services/assignableMembersService.ts (6.4) / the 5.1.6 view re-check — the recipient view-validation reused
  • lib/mentions/ — the excerpt/@Name render helper 5.1.6 already uses (no raw token leakage)
  • Story 5.4 (the work-item/transitioned watcher event — the watching fan-in slot) + Story 6.6 (work-item/created / work-item/field.changed) — the events that fan in via the registry seam later, with no 5.7 change