Skip to content

Note Posting & Streak Calculation Architecture

TIP

Interactive Architecture Tour: Open Live Tour (Note Posting Flow)

This document describes the end-to-end processing pipeline for study notes, timezone-aware streak evaluation, and dynamic user level computation in Scripture Habit.


1. Streak Calculation Engine (api_internal/lib/streak-engine.ts)

To accurately evaluate consistency across timezones and personal study routines, the system applies a hybrid calendar and grace-period engine.

Core Rules

Upon note submission, the engine evaluates the user's configured timeZone (defaults to 'UTC'):

  1. Local Date Resolution
    Converts server timestamps to the user's localized calendar date string (e.g., '2026-05-22') via the Node.js Intl API.

  2. Same-Day Duplicate Guard
    If the user's lastPostDate matches today's local date, the note is committed without incrementing the streak count (streakUpdated = false).

  3. Streak Continuity Evaluation
    On a new calendar day, the streak increments by +1 if either condition holds:

    • Consecutive Calendar Day: lastPostDate equals yesterday in the local timezone.
    • 36-Hour Grace Window: Time elapsed since the previous post is $\le$ 36 hours (hoursSinceLastPost <= 36).

    If neither condition is met, the streak resets to 1.

  4. All-Time Record Updating
    When the updated streak surpasses highestStreak, the record updates automatically.

Practical Scenarios

  • Late-Night Routine: Posting Monday at 8:00 AM and Tuesday at 10:00 PM (38 hours later) preserves the streak because calendar days are consecutive.
  • Transcontinental Travel: Crossing timezones and skipping a local calendar day is covered by the 36-hour physical window.
typescript
const isTargetDay = lastPostDate === yesterday;
const withinGracePeriod = lastTimeMillis > 0 && hoursSinceLastPost <= 36;

if (isTargetDay || withinGracePeriod) {
    newStreak += 1;
    isConsecutive = true;
} else {
    newStreak = 1; // Reset streak
}

2. Note Posting Transaction Flow

To guarantee state consistency, note submissions execute within an atomic Firestore transaction (db.runTransaction()):

  1. Membership Verification: Confirms group authorization via userData.groupIds.
  2. Streak Calculation: Resolves new streak counts and total study days via StreakEngine.
  3. Message Publishing: Writes the note payload into groups/{id}/messages.
  4. Master Note Archive: Saves a copy into users/{uid}/notes for the private library.
  5. User Profile Update: Updates totalNotes, lastPostAt, streakCount, and daysStudiedCount.
  6. Group Statistics Increment: Atomically increments counters via FieldValue.increment.

3. Post-Transaction Background Tasks

To maintain low commit latency, non-blocking tasks execute asynchronously after the transaction commits:

  1. Push Notifications: Retrieves member FCM tokens and broadcasts multicast notifications.
  2. Unity Score Recomputation: Asynchronously recalculates the group's daily participation score.

4. Database Operation Costs (5-Member Group Example)

Operational read/write costs for a single note submission within a 5-member group (1 author + 4 peers):

Writes (Total: 8)

  • Within Transaction (7 writes):
    1. User Profile (/users/{uid})
    2. Master Note Archive (/users/{uid}/notes/{noteId})
    3. Chat Message (/groups/{gid}/messages/{messageId})
    4. Group Metadata (/groups/{gid})
    5. Member Status (/groups/{gid}/members/{uid})
    6. User Group State (/users/{uid}/groupStates/{gid})
    7. Latest Message Cache (/groups/{gid}/messages_latest/latest)
  • Background Async (1 write): 8. Group Unity Status (/groups/{gid})

(Achieving a milestone adds +1 transaction write for the system announcement.)

Reads (Total: 7)

  • Within Transaction (2 reads): User Profile, Latest Message Cache
  • Background Async (5 reads): Group Metadata (1 read), Member FCM Tokens & Locales (4 reads)

5. Dynamic Level Computation

User level is derived dynamically from cumulative study days (daysStudiedCount):

$$\text{Level} = \lfloor \frac{\text{daysStudiedCount}}{7} \rfloor + 1$$

  • Weekly Progression: Every 7 unique study days advances the user by 1 level.
  • On-the-Fly Derivation: Computing levels on the client during render avoids database write overhead.

6. Sequence Diagram

Posting Sequence Breakdown

  1. Payload Dispatch
    The client submits reflection text, scripture references, and group visibility scopes to /api/messages/post-note.

  2. Timezone-Aware Evaluation
    StreakEngine evaluates local date continuity and the 36-hour grace window against the user's historical records.

  3. Atomic Commit & Client Feedback
    Personal archives, chat messages, and group rosters commit in a single transaction. Upon success, the client triggers celebration feedback.


7. Milestone Celebrations

To prevent demotivation when streaks break, the app celebrates Total Study Days (daysStudiedCount) as its primary retention driver:

  • Regular Days: Publishes a standard note notification to the group feed.
  • Milestones: Automatically publishes an official celebration card:
    • Initial Milestone: Day 10
    • Regular Cadence: Every 25 days thereafter (Day 25, 50, 75, 100, 125...).

See Milestone Celebrations & Retention Psychology for UX rationale.


Released under the MIT License.