February 10th, 2026
The Evolution of Infinite Scrolling: From Naive Time-Splits to State-Based Merging Today we finalized the architectural strategy for handling the complex requirements of an infinite calendar with recurring commitment chains. This document details the journey from initial concepts to the production-grade “Per-Task Merging” strategy.1. The Core Challenge
Problem: Calendars are theoretically infinite, but database queries must be finite.- Naive Approach: Fetch everything. (Fails at scale).
- Pagination: Clunky for browsing months/years.
- Solution: Sliding Window. Fetch only the visible range + buffer (e.g., ±14 days).
2. The Data Problem: Materialized vs. Virtual
Challenge: We have two distinct types of data that look the same on a UI.- Past Events (Real): Must be stored in DB (verified/failed state).
- Future Events (Virtual): Should be projected from rules. Storing 10 years of future rows is wasteful and dangerous (updates become nightmares).
- Past: Materialized rows in
taskInstances. - Future: Virtual Events generated on the fly from
tasksrecurrence rules.
3. The “Verification Chain” Complexity
CommitT is different from Google Calendar. Tasks are sequential chains:Instance 1 → Verify → Create Instance 2
Failed Approach: “Just-In-Time” Creation for any future event.
- Why: You can’t verify Instance #5 if Instance #3 hasn’t happened.
- Result: Future virtual events are read-only projections. Verification is strictly for the current pending instance in the chain.
4. The Critical Pivot: The “Gap” Problem
We initially tried a simple Time-Based Split:0 → Now: Fetch Real Instances.Now → ∞: Generate Virtual.
- If we cut fetching at “Now”, we miss the real pending instance.
- If we generate virtual from “Now”, we show a duplicate (real + virtual).
- Conclusion: There is no single “Global Cutoff Date”. Every task is in a different state.
5. The Final Strategy: “Per-Task Merging” (Industry Grade)
This approach maps closely to how Outlook handles recurrence exceptions.Components
- Query A: Fetch ALL instances in the visible window (Verified / Failed / Pending).
- Query B: Fetch ALL task rules.
Client-Side Merge Logic
We perform this operation in the frontend store:- Group Instances: Map real instances by
task_id. - Iterate Definitions: Loop through all Tasks.
- Find the Cutoff: For each specific task, find the end time of its last real instance in this window.
- Fill the Gap:
- Show all real instances.
- Start generating virtual events strictly after the last real instance’s end time.
Why this is “Industry Grade”
- N+1 Prevention: Only 2 network requests total.
- Accuracy: Respects the unique state of every commitment chain.
- Conflict Resolution: Real DB state (“I failed yesterday”) strictly overrides the Rule (“I do this daily”).
- Lazy Materialization: We respect the chain. The DB only holds what is real; the UI fills in the rest.
📅 Implementation Plan
Goal: Implement robust “Per-Task Merging”.Frontend (useCalendarStore.ts)
- Modify
loadEventsForRange:- Call
fetchInstancesfor the entire range. - Call
fetchTasks(if needed). - Implement
mergeInstancesAndRecurrence(instances, tasks, range):- Group by task.
- Merge real + virtual based on per-task cutoff.
- Call
Frontend (schedules.tsx)
- Simplify: Remove “split past/future” logic. Just pass the visible window.
Verification Plan
- Scroll Test: Verify past events appear correctly.
- Future Test: Verify infinite scroll works with virtual events.
- The “Gap” Test:
- Create a task with a pending instance today.
- Verify today shows Real Instance (interactive).
- Verify tomorrow shows Virtual Event (read-only).
- Crucial: Ensure NO duplicates appear.