Skip to content

feat(notifications): referral invite nudge banner at SessionStart#243

Merged
khustup2 merged 2 commits into
mainfrom
feat/referral-invite-banner
Jun 7, 2026
Merged

feat(notifications): referral invite nudge banner at SessionStart#243
khustup2 merged 2 commits into
mainfrom
feat/referral-invite-banner

Conversation

@khustup2
Copy link
Copy Markdown
Contributor

@khustup2 khustup2 commented Jun 7, 2026

What

A one-time SessionStart banner nudging signed-in users to invite teammates and earn the referral credit (pairs with the deeplake-api referral program — inviter's org gets $20 when an invited friend signs up).

💸 Invite a teammate — your org earns $20
Run hivemind invite <email> <ADMIN|WRITE|READ> — your org gets $20 in credit when they sign up (up to $100).

Cadence

  • Skips the first two sessions, fires on the 3rd (so brand-new users aren't nudged immediately).
  • Shows once ever (stable dedupKey; bump to re-nudge).
  • Only when signed in (you need an org to invite into).
  • Kill switch REFERRAL_NUDGE_ENABLED for dark-shipping — on, since referral is live on prod.

How

  • rules/referral-invite.ts: pure rule gated on creds + sessionCount >= 3.
  • Session counter in notifications-state.json: bumpSessionCount advances once per session, deduped by session_id so the two parallel SessionStart fires (settings.json + marketplace hooks.json) don't double-count. readState/markShown preserve the counter fields.
  • Hook entry registers the rule, bumps the counter, and passes sessionCount into the rule context (rules stay IO-free).

Client-only by design: not per-org-cap-aware (that's server state) — worst case it nudges an already-capped referrer once, which is fine for a single banner since "referral enabled" is global.

Tests

Rule cadence + sign-in gating, and bumpSessionCount (increment / same-id dedup / missing-id).

Note: the 2 pre-existing "built artifact" welcome bundle tests fail on clean main too (unrelated — welcome→additionalContext expectation), not touched here.

Follow-up

Needs a plugin version bump / release for users to receive it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a referral invitation banner shown to signed-in users starting on the 3rd session, shown exactly once with deduping.
  • Bug Fixes / Reliability
    • Session counting persisted and deduped so the banner cadence is consistent across restarts and duplicate session events.
  • Tests
    • Added tests covering the referral banner logic, session-count persistence, and dedupe behavior.
  • Chores
    • Added a coverage threshold for the new rule.

A one-time SessionStart banner telling signed-in users they earn $20 of org
credit for inviting teammates (pairs with the deeplake-api referral program).

- new rules/referral-invite.ts: fires once, from the 3rd session on (skips the
  first two), only when signed in; stable dedupKey shows it a single time.
  Kill switch (REFERRAL_NUDGE_ENABLED) for dark-shipping — on, since referral
  is live.
- session counter in notifications-state.json: bumpSessionCount advances once
  per session, deduped by session_id (the two parallel SessionStart fires don't
  double-count); state read/markShown now preserve the counter fields.
- hook entry registers the rule, bumps the counter, passes sessionCount into the
  rule context (rules stay IO-free).

Tests: rule cadence/sign-in gating + bumpSessionCount (increment, dedup,
missing-id). Pre-existing "built artifact" welcome bundle tests are unrelated
(fail on clean main).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 01dcf5fd-e615-4250-bc57-74ad77b3fb61

📥 Commits

Reviewing files that changed from the base of the PR and between 976e49d and ca99cc0.

📒 Files selected for processing (3)
  • src/notifications/rules/referral-invite.ts
  • tests/claude-code/notifications-referral-invite.test.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (1)
  • vitest.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/claude-code/notifications-referral-invite.test.ts

📝 Walkthrough

Walkthrough

This PR adds persistent session counting to notifications, increments the count once per distinct session_id, persists it, and passes sessionCount into the notification context so rules can fire after N sessions without IO.

Changes

Notification Session Cadence Feature

Layer / File(s) Summary
Type contracts for session tracking
src/notifications/types.ts
NotificationContext and NotificationsState gain sessionCount and lastCountedSessionId fields to track distinct sessions per install.
Session state persistence
src/notifications/state.ts
readState() restores counters; bumpSessionCount() advances sessionCount once per unique session_id; markShown() preserves counters when recording shown notifications.
Session count through notification pipeline
src/notifications/index.ts
DrainOptions adds optional sessionCount and drainSessionStart() propagates it into NotificationContext used by rule evaluation.
Referral invite rule with session cadence
src/notifications/rules/referral-invite.ts
New referralInviteRule fires an informational invite banner on session_start only for signed-in users when sessionCount >= 3, with a stable dedupKey.
Hook entry point session counting
src/hooks/session-notifications.ts
Hook registers referralInviteRule, calls bumpSessionCount(sessionId), and passes the returned sessionCount into drainSessionStart().
Tests for session cadence and referral rule
tests/claude-code/notifications-referral-invite.test.ts
Adds tests for referralInviteRule behavior across sessions and for bumpSessionCount persistence, deduping, legacy-state handling, and markShown preservation.
Vitest coverage threshold
vitest.config.ts
Adds a per-file coverage threshold entry for src/notifications/rules/referral-invite.ts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • activeloopai/hivemind#96: Introduces the centralized notification framework that this PR extends with session counting.
  • activeloopai/hivemind#128: Also modifies the session-start notification pipeline to pass session-scoped data into drainSessionStart.
  • activeloopai/hivemind#223: Related hook integration changes affecting session-start rule registration and options passed into drainSessionStart.

Suggested reviewers

  • kaghni
  • efenocchi

Poem

🐰 I counted sessions, one, two, three—
A banner waits for friends to see.
One bump per id, state kept tight,
A rabbit cheers: "Invite delight!" 🥕🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the key implementation details (cadence, gating, session counter mechanism, tests) but is missing the version bump required by the template. Add a version bump section to package.json following the template. Since this is a new feature, bump the minor version (e.g., 1.2.0 → 1.3.0).
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a referral invite notification banner triggered at session start.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/referral-invite-banner

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 98.98% (🎯 90%) 97 / 98
🟢 Statements 98.26% (🎯 90%) 113 / 115
🟢 Functions 94.74% (🎯 90%) 18 / 19
🔴 Branches 89.71% (🎯 90%) 61 / 68
File Coverage — 5 files changed
File Stmts Branches Functions Lines
src/hooks/session-notifications.ts 🟢 93.8% 🟢 90.0% 🔴 75.0% 🟢 100.0%
src/notifications/index.ts 🟢 100.0% 🟢 95.0% 🟢 100.0% 🟢 100.0%
src/notifications/rules/referral-invite.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/notifications/state.ts 🟢 98.2% 🔴 84.4% 🟢 100.0% 🟢 98.1%
src/notifications/types.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%

Generated for commit 609ab70.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/claude-code/notifications-referral-invite.test.ts (1)

40-41: ⚡ Quick win

Strengthen the “past 3rd session” assertion with payload identity.

At Line 41, not.toBeNull() validates firing but not that the correct notification is returned. A toMatchObject({ id: "referral-invite", dedupKey: { v: 1 } }) check would better lock contract behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-code/notifications-referral-invite.test.ts` around lines 40 -
41, Replace the loose null-check with a strict payload identity assertion: call
referralInviteRule.evaluate(ctx({ creds: signedIn, sessionCount: 9 })) and
assert the returned object matches the expected notification shape (e.g.,
toMatchObject({ id: "referral-invite", dedupKey: { v: 1 } })) so the test
verifies both firing and the exact notification payload rather than only
non-null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/notifications/state.ts`:
- Around line 53-61: bumpSessionCount currently does an unlocked
read-modify-write using readState() and writeState(), which can drop concurrent
increments; wrap the increment/write portion in cross-process synchronization by
either (a) acquiring a named lock (e.g., lock file) before calling readState()
and releasing it after writeState(), or (b) implementing a CAS-style retry loop:
readState(), compute next only if lastCountedSessionId differs, attempt atomic
write (or writeState only if persisted state unchanged), and retry a few times
on conflict; ensure you still respect the early-return when sessionId is falsy
or equals lastCountedSessionId and reference the bumpSessionCount, readState,
writeState and lastCountedSessionId symbols when making the change.

In `@tests/claude-code/notifications-referral-invite.test.ts`:
- Around line 35-36: Replace the substring assertions on the notification object
with exact-string assertions: change the two expects that reference n!.title and
n!.body from toContain(...) to toEqual(...) (or toBe(...)) and supply the full
expected title and body literal strings (the current correct user-facing copy)
instead of "$20" and "hivemind invite"; update the test's expected values for
n!.title and n!.body to the exact messages used by the code under test so the
test fails on any text regression.

---

Nitpick comments:
In `@tests/claude-code/notifications-referral-invite.test.ts`:
- Around line 40-41: Replace the loose null-check with a strict payload identity
assertion: call referralInviteRule.evaluate(ctx({ creds: signedIn, sessionCount:
9 })) and assert the returned object matches the expected notification shape
(e.g., toMatchObject({ id: "referral-invite", dedupKey: { v: 1 } })) so the test
verifies both firing and the exact notification payload rather than only
non-null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9388869a-ed36-4570-8ecb-9d7695ce85d7

📥 Commits

Reviewing files that changed from the base of the PR and between 0b35aec and 976e49d.

📒 Files selected for processing (6)
  • src/hooks/session-notifications.ts
  • src/notifications/index.ts
  • src/notifications/rules/referral-invite.ts
  • src/notifications/state.ts
  • src/notifications/types.ts
  • tests/claude-code/notifications-referral-invite.test.ts

Comment thread src/notifications/state.ts
Comment thread tests/claude-code/notifications-referral-invite.test.ts Outdated
@khustup2 khustup2 requested review from efenocchi and kaghni June 7, 2026 06:17
- referral-invite rule: drop the always-true REFERRAL_NUDGE_ENABLED kill switch
  (dead branch hurting branch coverage); disabling = unregister the rule.
- tests: exact-string assertions on the banner copy (per coding guidelines);
  add state-counter coverage — writeState/readState round-trip, legacy state
  (no counter), and markShown preserving the counter fields.
- vitest.config: add per-file coverage thresholds for the new rule file.
@khustup2 khustup2 merged commit 27f5956 into main Jun 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants