Skip to content

Commit 1b25bed

Browse files
committed
chore: complete v2.2 Session Limits milestone
Archive milestone artifacts (roadmap, requirements, audit, phases) to milestones/v2.2-*, update PROJECT.md with validated requirements and key decisions, reorganize ROADMAP.md, write retrospective. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 841ba52 commit 1b25bed

17 files changed

Lines changed: 332 additions & 63 deletions

File tree

.planning/MILESTONES.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
# Milestones: Lumio
22

3+
## v2.2 Session Limits (Shipped: 2026-03-05)
4+
5+
**Delivered:** Session card limit enforcement end-to-end — RPC caps cards to user-chosen limit with overdue-first priority, dashboard counter reflects session-limited count, and "Auto" label replaces infinity symbol.
6+
7+
**Phases completed:** 32-33 (2 plans total)
8+
9+
**Key accomplishments:**
10+
11+
- RPC `get_study_cards_for_session` enforces total card cap with overdue-first priority (IF/ELSE plpgsql branching for NULL vs capped p_limit)
12+
- RPC `get_due_card_count` returns session-aware count via LEAST(total, p_limit) for dashboard
13+
- Dashboard counter reflects session-limited card count, not total backlog (reactive via useStudySettings)
14+
- CardsPerSession type renamed from 'all' to 'auto' with backward-compatible AsyncStorage migration
15+
- Settings selector shows "Auto" with sparkles icon replacing "All cards" / infinity symbol
16+
17+
**Stats:**
18+
19+
- 10 files changed (+318 / -20 lines)
20+
- 2 phases, 2 plans, 4 tasks
21+
- 2 days (2026-03-04 to 2026-03-05)
22+
23+
**Git range:** `153e1d9``eccc6a1` (4 feat commits)
24+
25+
**What's next:** TBD
26+
27+
---
28+
329
## v1.1 Lumio Native (Shipped: 2026-02-08)
430

531
**Delivered:** Complete migration from dual PWA apps to a single native Android application with landing page and CI/CD pipeline.

.planning/PROJECT.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,14 @@ Gli utenti studiano concetti tramite quiz generati dall'AI — il contenuto vien
8585
- ✓ 71 chiavi i18n per auth in IT e EN con validazione DeepStringify a compile-time — v2.1
8686
- ✓ SignOut guard per utenti email-only (GoogleSignin.signOut condizionale) — v2.1
8787
- ✓ Recovery state machine con persistenza AsyncStorage per reset password — v2.1
88+
- ✓ RPC cap rigido sessione: scadute first (più vecchie prima), poi nuove, totale mai oltre p_limit — v2.2
89+
- ✓ Dashboard counter mostra carte della prossima sessione (LEAST capping), non debito totale — v2.2
90+
- ✓ Selettore "Auto" con icona sparkles al posto di "Tutte/∞" — v2.2
91+
- ✓ Backward-compatible AsyncStorage migration da 'all' a 'auto' — v2.2
8892

8993
### Active
9094

91-
## Current Milestone: v2.2 Session Limits
92-
93-
**Goal:** Rispettare il limite carte-per-sessione scelto dall'utente, con dashboard coerente e label "Auto" per il selettore.
94-
95-
**Target features:**
96-
- Cap rigido RPC: scadute first (più vecchie prima), poi nuove, totale mai oltre il limite scelto
97-
- Dashboard counter mostra carte della prossima sessione (non debito totale)
98-
- Selettore: rinomina "Tutte/∞" → "Auto" (stessa logica, label diversa)
95+
(None — planning next milestone)
9996

10097
### Out of Scope
10198

@@ -126,16 +123,16 @@ Gli utenti studiano concetti tramite quiz generati dall'AI — il contenuto vien
126123

127124
## Context
128125

129-
**Stato attuale (post v2.1):**
126+
**Stato attuale (post v2.2):**
130127
- Monorepo pnpm: apps/android (Expo/React Native), apps/landing (static HTML), packages/core, packages/shared
131128
- Backend Supabase: auth (Google OAuth + email/password), DB, storage, edge functions, Docora webhook + study_sessions + card_review_schedule tables
132129
- Tech stack: Expo SDK 54, React Native 0.81, react-navigation, @lumio/core, i18n-js, react-native-marked, supermemo@2.0.23, vitest@4.0.18
133130
- CI/CD: lint → build-apk → deploy-landing → deploy-migrations → deploy-functions (version from STATE.md)
134131
- Versioning: STATE.md milestone → extract-version.cjs → version.ts, APK versionName, landing page, edge function
135132
- Auth: dual-mode Google OAuth + email/password con OTP verification, password reset, account linking bidirezionale
136-
- App bilingue IT/EN con branding Lumio, ripetizione spaziata SM-2, sessioni configurabili, card browse, study history con conteggio carte, studio forward-only, sync error handling con token update in-app
137-
- ~26,000 LOC (TS/TSX/SQL) — +9,897 lines in v2.1
138-
- 9 milestones shipped: v1.1 (native app), v1.2 (polish & i18n), v1.3 (bugfix & UX), v1.4 (card browse & stats), v1.5 (study UX fixes), v1.6 (sync error handling), v1.7 (GSD versioning), v2.0 (spaced repetition), v2.1 (email auth)
133+
- App bilingue IT/EN con branding Lumio, ripetizione spaziata SM-2, sessioni configurabili con cap RPC, card browse, study history con conteggio carte, studio forward-only, sync error handling con token update in-app, dashboard session-aware
134+
- ~19,800 LOC (TS/TSX/SQL)
135+
- 10 milestones shipped: v1.1 (native app), v1.2 (polish & i18n), v1.3 (bugfix & UX), v1.4 (card browse & stats), v1.5 (study UX fixes), v1.6 (sync error handling), v1.7 (GSD versioning), v2.0 (spaced repetition), v2.1 (email auth), v2.2 (session limits)
139136

140137
## Constraints
141138

@@ -210,6 +207,11 @@ Gli utenti studiano concetti tramite quiz generati dall'AI — il contenuto vien
210207
| addPasswordModeRef to suppress PASSWORD_RECOVERY | Prevents recovery nav during add-password OTP flow | ✓ Good — v2.1 |
211208
| Supabase linkIdentity with queryParams | Google token exchange for account linking | ✓ Good — v2.1 |
212209
| Session JSON size logging after identity change | SecureStore monitoring for dual-identity JWT stability | ✓ Good — v2.1 |
210+
| IF/ELSE in plpgsql for NULL vs non-NULL p_limit | Cleaner than COALESCE with large sentinel for unlimited vs capped logic | ✓ Good — v2.2 |
211+
| p_limit DEFAULT NULL for RPC parameters | Matches production unlimited behavior as safe default | ✓ Good — v2.2 |
212+
| LEAST(total, p_limit) for count capping | Simpler than IF/ELSE with separate queries for scalar cap | ✓ Good — v2.2 |
213+
| Hardcoded 'Auto' label (universal across languages) | "Auto" is understood in both IT and EN, no translation needed | ✓ Good — v2.2 |
214+
| AsyncStorage backward-compat migration 'all' → 'auto' | Read old value, return new enum value silently | ✓ Good — v2.2 |
213215

214216
---
215-
*Last updated: 2026-03-04 after v2.2 milestone start*
217+
*Last updated: 2026-03-05 after v2.2 milestone*

.planning/RETROSPECTIVE.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,108 @@
4646

4747
---
4848

49+
## Milestone: v2.1 — Email Auth
50+
51+
**Shipped:** 2026-03-02
52+
**Phases:** 5 | **Plans:** 10 | **Sessions:** ~5
53+
54+
### What Was Built
55+
- Email/password signup with 6-digit OTP verification via branded Lumio email templates
56+
- Email login with progressive disclosure UX (Google OAuth on top, email below separator)
57+
- Password reset with two-phase OTP-then-password screen and global session invalidation
58+
- Bidirectional account linking (Google ↔ email) with single-identity unlink protection
59+
- Provider-aware database trigger for email signups (display_name from email prefix)
60+
- 71 auth i18n keys in IT/EN with DeepStringify compile-time validation
61+
62+
### What Worked
63+
- OTP over deep link was the right choice for Android — no deep link infrastructure needed, more reliable
64+
- Progressive disclosure pattern reduces cognitive load on login screen
65+
- Recovery state machine with AsyncStorage survives app restarts elegantly
66+
- addPasswordModeRef pattern cleanly separates "add password" from "forgot password" OTP flows
67+
68+
### What Was Inefficient
69+
- Dead else branch in ForgotPasswordScreen.tsx error handling — both paths set same error message (minor tech debt)
70+
- Multiple quick tasks (5, 6, 8, 9) needed post-milestone for production issues — could benefit from more thorough UAT before shipping
71+
72+
### Patterns Established
73+
- Provider-aware trigger using `raw_app_meta_data->>'provider'` with COALESCE default
74+
- Guard pattern: `hasPreviousSignIn()` before `GoogleSignin.signOut()` for mixed-auth users
75+
- Two-phase screen pattern: single component handles both OTP entry and action (password set, verification)
76+
- Ref-based flow suppression (`addPasswordModeRef`) to prevent auth event handler interference
77+
78+
### Key Lessons
79+
1. OTP is more reliable than deep links on Android — less infrastructure, fewer failure modes
80+
2. Auth flows need extensive real-device testing — several quick fixes were needed post-ship
81+
3. Provider-aware triggers with explicit detection are cleaner than implicit auth.users field checks
82+
4. Global session invalidation on password change is a security best practice worth the UX cost
83+
84+
### Cost Observations
85+
- Model mix: 80% opus, 20% sonnet (quality profile)
86+
- Sessions: ~5 (4 days of development)
87+
- Notable: 5 phases, 10 plans, 16 requirements — largest milestone since v1.1, all satisfied
88+
89+
---
90+
91+
## Milestone: v2.2 — Session Limits
92+
93+
**Shipped:** 2026-03-05
94+
**Phases:** 2 | **Plans:** 2 | **Sessions:** ~2
95+
96+
### What Was Built
97+
- RPC `get_study_cards_for_session` enforces total card cap with overdue-first priority (IF/ELSE plpgsql for NULL vs capped p_limit)
98+
- RPC `get_due_card_count` returns session-aware count via LEAST(total, p_limit) for dashboard
99+
- Dashboard counter reflects session-limited card count reactively via useStudySettings
100+
- CardsPerSession type renamed from 'all' to 'auto' with backward-compatible AsyncStorage migration
101+
- Settings selector shows "Auto" with sparkles icon
102+
103+
### What Worked
104+
- Minimal milestone scope (2 phases, 5 requirements) — shipped in 2 days with zero deviations
105+
- Nullable RPC parameter pattern (p_limit DEFAULT NULL) from Phase 32 reused immediately in Phase 33
106+
- LEAST(total, p_limit) was simpler than duplicating query logic for the count RPC
107+
- Audit passed cleanly: 5/5 requirements, 8/8 integration checks, 3/3 E2E flows
108+
109+
### What Was Inefficient
110+
- Nothing notable — clean execution from start to finish
111+
112+
### Patterns Established
113+
- Nullable RPC parameters: pass null from TS, PostgreSQL uses DEFAULT NULL for unlimited behavior
114+
- AsyncStorage backward-compat migration: read old value, return new enum value silently
115+
- LEAST-based capping for scalar count RPCs (simpler than IF/ELSE when only capping a result)
116+
117+
### Key Lessons
118+
1. Small, focused milestones (2 phases) execute cleanly with near-zero overhead
119+
2. Reusing RPC patterns across phases in the same milestone accelerates development
120+
3. "Auto" as universal label (no translation needed) simplifies i18n
121+
122+
### Cost Observations
123+
- Model mix: 80% opus, 20% sonnet (quality profile)
124+
- Sessions: ~2 (2 days)
125+
- Notable: Smallest milestone since v1.5 — 2 phases, 2 plans, 4 tasks, ~6 minutes total execution
126+
127+
---
128+
49129
## Cross-Milestone Trends
50130

51131
### Process Evolution
52132

53133
| Milestone | Sessions | Phases | Key Change |
54134
|-----------|----------|--------|------------|
55135
| v2.0 | ~4 | 4 | First milestone with audit-before-complete workflow; gap closure pattern |
136+
| v2.1 | ~5 | 5 | Largest since v1.1; post-ship quick fixes revealed UAT gaps |
137+
| v2.2 | ~2 | 2 | Cleanest milestone — zero deviations, zero issues |
56138

57139
### Cumulative Quality
58140

59141
| Milestone | Tests | Coverage | Zero-Dep Additions |
60142
|-----------|-------|----------|-------------------|
61143
| v2.0 | 10 (SM-2 unit) | SRS module only | supermemo@2.0.23, vitest@4.0.18 |
144+
| v2.1 || Auth flows (manual) ||
145+
| v2.2 || Session limit (manual) ||
62146

63147
### Top Lessons (Verified Across Milestones)
64148

65149
1. Research-first planning pays off — prevents mid-milestone pivots (validated v1.1 through v2.0)
66-
2. Server-side computation for stateful operations avoids race conditions and simplifies client code (v2.0)
150+
2. Server-side computation for stateful operations avoids race conditions and simplifies client code (v2.0, v2.2)
67151
3. Small gap closure plans are more efficient than trying to get everything right in the first pass (v2.0)
152+
4. Small, focused milestones execute cleanly with near-zero overhead (v2.2)
153+
5. Reusing patterns across phases in the same milestone accelerates development (v2.2)

.planning/ROADMAP.md

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
-**v1.7 GSD Versioning** — Phases 20-22 (shipped 2026-02-21)
1212
-**v2.0 Spaced Repetition** — Phases 23-26 (shipped 2026-02-26)
1313
-**v2.1 Email Auth** — Phases 27-31 (shipped 2026-03-02)
14-
- 🚧 **v2.2 Session Limits** — Phases 32-33 (in progress)
14+
- **v2.2 Session Limits** — Phases 32-33 (shipped 2026-03-05)
1515

1616
## Phases
1717

@@ -118,47 +118,18 @@ Full details: `.planning/milestones/v2.1-ROADMAP.md`
118118

119119
</details>
120120

121-
### v2.2 Session Limits (In Progress)
122-
123-
**Milestone Goal:** Rispettare il limite carte-per-sessione scelto dall'utente, con dashboard coerente e label "Auto" per il selettore.
124-
125-
- [x] **Phase 32: RPC Session Limit Enforcement** - Backend RPC caps cards to chosen limit with overdue-first priority (completed 2026-03-04)
126-
- [x] **Phase 33: Dashboard Counter & Auto Label** - Dashboard reflects session-limited count and selector shows "Auto" (completed 2026-03-05)
127-
128-
## Phase Details
129-
130-
### Phase 32: RPC Session Limit Enforcement
131-
**Goal**: Study sessions deliver exactly the number of cards the user chose, prioritizing overdue cards
132-
**Depends on**: Nothing (first phase of v2.2)
133-
**Requirements**: SESS-01, SESS-02
134-
**Success Criteria** (what must be TRUE):
135-
1. User who selects "20 cards" receives at most 20 cards in a study session, even if 50 are available
136-
2. Overdue cards always appear before new cards within the capped session (oldest overdue first)
137-
3. User with "Auto" selected receives all available cards (overdue + new) with no cap applied
138-
4. When fewer cards exist than the chosen limit, all available cards are returned without error
139-
**Plans**: 1 plan
121+
<details>
122+
<summary>✅ v2.2 Session Limits (Phases 32-33) — SHIPPED 2026-03-05</summary>
140123

141-
Plans:
142-
- [ ] 32-01-PLAN.md — Enforce session limit in RPC, rename CardsPerSession type, update hook
124+
- [x] Phase 32: RPC Session Limit Enforcement (1 plan) — completed 2026-03-04
125+
- [x] Phase 33: Dashboard Counter & Auto Label (1 plan) — completed 2026-03-05
143126

144-
### Phase 33: Dashboard Counter & Auto Label
145-
**Goal**: Dashboard and session selector accurately reflect the session-limited experience
146-
**Depends on**: Phase 32
147-
**Requirements**: DASH-01, DASH-02, UI-01
148-
**Success Criteria** (what must be TRUE):
149-
1. Dashboard counter shows the number of cards the user will actually study in their next session (respecting chosen limit), not the total backlog
150-
2. User with "Auto" selected sees the full count of all available cards on the dashboard
151-
3. Session size selector displays "Auto" instead of "Tutte" or the infinity symbol for the unlimited option
152-
**Plans**: 1 plan
127+
Full details: `.planning/milestones/v2.2-ROADMAP.md`
153128

154-
Plans:
155-
- [ ] 33-01-PLAN.md — Session-aware dashboard counter, Auto label with sparkles icon
129+
</details>
156130

157131
## Progress
158132

159-
**Execution Order:**
160-
Phases execute in numeric order: 32 -> 33
161-
162133
| Phase | Milestone | Plans Complete | Status | Completed |
163134
|-------|-----------|----------------|--------|-----------|
164135
| 1-5. Foundation to Cleanup | v1.1 | 20/20 | Complete | 2026-02-08 |
@@ -170,9 +141,8 @@ Phases execute in numeric order: 32 -> 33
170141
| 20-22. GSD Versioning | v1.7 | 6/6 | Complete | 2026-02-21 |
171142
| 23-26. Spaced Repetition | v2.0 | 8/8 | Complete | 2026-02-26 |
172143
| 27-31. Email Auth | v2.1 | 10/10 | Complete | 2026-03-02 |
173-
| 32. RPC Session Limit Enforcement | 1/1 | Complete | 2026-03-04 | - |
174-
| 33. Dashboard Counter & Auto Label | 1/1 | Complete | 2026-03-05 | - |
144+
| 32-33. Session Limits | v2.2 | 2/2 | Complete | 2026-03-05 |
175145

176146
---
177147
*Roadmap created: 2026-01-29*
178-
*v2.2 roadmap added: 2026-03-04*
148+
*v2.2 shipped: 2026-03-05*

.planning/STATE.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ milestone: v2.2
44
milestone_name: Session Limits
55
status: completed
66
stopped_at: Completed 33-01 dashboard counter auto label
7-
last_updated: "2026-03-05T09:19:33.676Z"
7+
last_updated: "2026-03-05T11:21:15.974Z"
88
last_activity: 2026-03-05 — Completed 33-01 dashboard counter auto label
99
progress:
1010
total_phases: 2
@@ -18,10 +18,10 @@ progress:
1818

1919
## Project Reference
2020

21-
See: .planning/PROJECT.md (updated 2026-03-04)
21+
See: .planning/PROJECT.md (updated 2026-03-05)
2222

2323
**Core value:** Gli utenti studiano concetti tramite quiz generati dall'AI -- il contenuto viene dai repository Git, le domande vengono generate e pre-cachate dal sistema.
24-
**Current focus:** v2.2 Session Limits — Phase 33 plan 01 complete (milestone complete)
24+
**Current focus:** v2.2 shipped — planning next milestone
2525

2626
## Current Position
2727

@@ -35,9 +35,9 @@ Progress: [██████████] 100%
3535
## Performance Metrics
3636

3737
**Velocity:**
38-
- Total plans completed: 66 (across v1.1-v2.1)
39-
- Total milestones shipped: 9
40-
- Timeline: 33 days (2026-01-29 to 2026-03-02)
38+
- Total plans completed: 68 (across v1.1-v2.2)
39+
- Total milestones shipped: 10
40+
- Timeline: 35 days (2026-01-29 to 2026-03-05)
4141

4242
| Phase | Plan | Duration | Tasks | Files |
4343
|-------|------|----------|-------|-------|
@@ -58,17 +58,15 @@ Progress: [██████████] 100%
5858

5959
### Decisions
6060

61-
All decisions logged in PROJECT.md Key Decisions table (61 entries).
62-
- [Phase 32]: IF/ELSE in plpgsql for NULL vs non-NULL p_limit; p_limit DEFAULT NULL matches production unlimited behavior
63-
- [Phase 33]: LEAST(total, p_limit) for count capping in get_due_card_count; hardcoded 'Auto' label (universal across languages)
61+
All decisions logged in PROJECT.md Key Decisions table (66 entries).
6462

6563
### Pending Todos
6664

6765
None.
6866

6967
### Blockers/Concerns
7068

71-
None — v2.1 milestone audit passed with 16/16 requirements satisfied.
69+
None — v2.2 milestone audit passed with 5/5 requirements satisfied.
7270

7371
### Quick Tasks Completed
7472

File renamed without changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# Requirements Archive: v2.2 Session Limits
2+
3+
**Archived:** 2026-03-05
4+
**Status:** SHIPPED
5+
6+
For current requirements, see `.planning/REQUIREMENTS.md`.
7+
8+
---
9+
110
# Requirements: Lumio
211

312
**Defined:** 2026-03-04

0 commit comments

Comments
 (0)