{"repo": "axif0/study-plan", "files": {"study-wellbeing-app-PRD.md": {"00b230fd699ed101d40a1da37af26748be9a87ee": {"t": 1785595267.8195398, "n": 45466, "h": "afb069a198735c57", "c": "# Product Requirements Document (PRD)\n## Nigakoi — Personalized Study & Digital Wellbeing App\n\n| Field | Value |\n|---|---|\n| **Version** | 2.4 |\n| **App version** | 0.1.0 (`versionCode` 1) |\n| **Launcher name** | Nigakoi |\n| **Internal / project name** | StudyCoach |\n| **Package** | `com.studycoach.app` |\n| **Platform** | Native Android (Kotlin + Jetpack Compose) |\n| **minSdk / targetSdk / compileSdk** | 26 / 35 / 35 |\n| **Status** | Shipped local v0.1 + engagement (§6.10) + vault habit enrichment (v2.4) — canonical product spec |\n| **Supersedes** | PRD v2.3; PRD v2.2; PRD v2.1; PRD v2.0; PRD v1.2 (Draft) |\n\n> **Implementation note:** Build native Android — Accessibility Service (§6.3) requires it. Use the schema in §8 as the single source of truth (Room DB). Every LLM call re-serializes a trimmed slice of this schema into the prompt; never rely on LLM conversation memory.\n>\n> **v2.4 rule:** Where code and older draft PRD disagree, **code is truth**. Original v1.2 intent that is not shipped lives under §11 Deferred / Future. Status tags: **[Shipped]** | **[Partial]** | **[Deferred]**.\n>\n> **v2.1 delta:** Phrase pools + no-repeat-last-3; light tie shuffle; “why this now” reason lines; pre-session phone hook; CHUNK subject interleave; variable-ratio bonus + weekly streak freeze. All offline / LocalCoach — no new network or permissions.\n>\n> **v2.2 delta:** Pomodoro work/break duration derives from today’s `energy_log` check-in (`LocalCoach.pomodoroDurationsFor`) — low → 15/3, ok/unset → 25/5, high → 30/5. Phone-Down target stays fixed 25 min. No schema migration.\n>\n> **v2.3 delta:** Wire LLM `performanceOneLiner` + `distractionInsight` (once/day → `cached_phrases`, LocalCoach fallback); Pomodoro elapsed work → `StudySession.duration_minutes` on swipe-done; same-day focus momentum badge; hotspot-aware nudge cooldown; derived `WeeklyQuest` on Overall. **No schema migration.**\n>\n> **v2.4 delta:** Vault habit enrichment — `HabitStore.suggestDailyCap` / week trend from last-90 entries; Banglish `LocalCoach.habitMessage` PhrasePick pools (vault-only); optional Banglish LLM upgrade with history payload. `h.json` still isolated from study `trimCtx`. **No Room schema migration.**\n\n---\n\n## 1. Problem Statement\n\nStudents and job-exam candidates get distracted by social media (e.g. Facebook via Chrome) while trying to study. Generic digital-wellbeing reminders are ignored because they are not personalized and do not understand:\n\n- What the user is actually studying\n- Which topics are weak and need repeated revision\n- The user’s own behavior pattern (when / why they get distracted)\n- Curated external content (YouTube channels, websites) they *want* to follow — without falling into the platform’s distraction trap (algorithmic feed, recommended videos)\n\nThe app must feel like a **personal study coach**, not a generic screen-time blocker — and it must **survive LLM API key expiry** without losing what it has learned about the user.\n\n**Cultural layer (shipped):** Bangla–English UX copy, partner/friend coach tones, and a daily completion ritual (“Umma”). Brand name **Nigakoi** on launcher, notifications, setup, and widget.\n\n---\n\n## 2. Goals\n\n| Goal | Success looks like | Status |\n|---|---|---|\n| Reduce distracted scrolling (FB via Chrome / apps) | Contextual nudges at the right moment, in the user’s tone | **[Shipped]** UsageAccess + Accessibility |\n| Build a personalized study plan without heavy effort | Syllabus OCR / paste / manual → structured subjects & topics | **[Shipped]** LLM optional; local parse fallback |\n| Daily work without rigid auto-calendar | Monthly topic pool + derived / manual daily picks | **[Shipped]** Auto calendar **disabled** |\n| No quizzes / tests — zero “teaching” burden | App organizes / schedules only; content stays user-driven | **[Shipped]** |\n| Persistent personalization independent of LLM key | All personal data in app DB; new key = instant continuity | **[Shipped]** Room + EncryptedSharedPreferences for key |\n| Curated, distraction-free external content | Followed sites/channels → update cards only, no open feed | **[Partial]** RSS on open; YouTube needs API key UI |\n| Smart, importance-weighted revision | High-priority / shaky topics recur sooner | **[Shipped]** Explicit intervals §6.5 |\n| Focus tools that reduce phone pull | Phone-down timer, Pomodoro | **[Shipped]** |\n| Offline coach continuity | LocalCoach phrase pools when key missing / call fails | **[Shipped]** |\n| Reduce study-flow boredom | Varied coach copy, tie shuffle, reason lines, bonus flourish, micro-quest, momentum | **[Shipped]** §6.10 |\n| Protect streak fairness | Once-a-week freeze for exactly one miss | **[Shipped]** §6.5 / §6.10 |\n| Right-time distraction nudges | Hotspot-aware cooldown — rarer outside windows, prioritize hour before risk | **[Shipped]** §6.3 |\n\n---\n\n## 3. Non-Goals (explicitly out of scope)\n\n- No quizzes, tests, or grading of any kind\n- No AI “teaching” / explaining concepts inside the app (app organizes / schedules only)\n- No open-ended social feed browsing (YouTube / website feeds are never shown — only followed-item updates)\n- No dependency on the LLM for remembering user history (LLM is stateless; context sent each call)\n- No light theme (always dark)\n- No iOS / web v1 (Accessibility monitoring is Android-specific)\n- No cloud sync in v1 (§11)\n\n---\n\n## 4. Users\n\n**Primary:** Self-learners preparing for a job exam / academic syllabus within a fixed deadline (typically 2–3 months), prone to social-media distraction, who want a personalized, non-clinical study companion that speaks in a Bangla–English mix and optional affectionate / strict friend register.\n\n**Defaults in schema (editable in Settings):** height 177 cm, weight 80 kg — used only for grounded accountability in vault / LLM context, never for body-shaming.\n\n---\n\n## 5. Core Architecture Principle\n\n> **The LLM is a disposable tool. The app’s database is the brain.**\n\n- Behavior, plan, weak topics, preferences, and history live **locally** (Room `nigakoi.db` / profile document). API keys live in **EncryptedSharedPreferences**, never in profile JSON.\n- Every LLM call sends a **trimmed context payload** derived from §8 — not raw distraction event timestamps.\n- If the API key expires and the user adds a new one (possibly a different OpenAI-compatible provider), the new model “knows” the user because context comes from the DB.\n- **Provider-agnostic:** any OpenAI-compatible `/chat/completions` endpoint works via Settings (base URL + model + key).\n- **LocalCoach** supplies Bangla–English phrase **pools** (6–10 variants per trigger × tone) when the key is absent or the call fails; in-memory **no-repeat-last-3** phrase IDs avoid copy fatigue. Cached LLM phrases in `cached_phrases` survive key expiry for display.\n\n```mermaid\nflowchart LR\n subgraph brain [Local brain]\n RoomDB[nigakoi.db profile]\n HabitFile[h.json vault]\n KeyStore[EncryptedSharedPreferences]\n end\n UI[Compose UI]\n LocalCoach[LocalCoach fallback]\n LLM[OpenAI-compatible API]\n Monitors[UsageMonitor / Accessibility]\n UI --> RoomDB\n UI --> HabitFile\n Monitors --> RoomDB\n UI --> LocalCoach\n UI -->|\"trimmed JSON + prompt\"| LLM\n Monitors -->|\"nudge\"| LLM\n KeyStore -->|\"Authorization\"| LLM\n LLM -.->|\"optional upgrade\"| UI\n```\n\n---\n\n## 6. Feature Set\n\n### 6.0 Navigation & shell **[Shipped]**\n\n**Entry:** `MainActivity` → `StudyCoachTheme` → `StudyCoachNav`.\n\n| Condition | Start route |\n|---|---|\n| `!setup_complete` | `setup` |\n| else | `daily` |\n\n**Bottom nav (5 tabs)** — hidden on `setup`, `a11y`, `vault`:\n\n| Route | Label | Icon |\n|---|---|---|\n| `daily` | Daily | Today |\n| `monthly` | Monthly | CalendarMonth |\n| `overall` | Overall | Dashboard |\n| `topics` | Topics | List |\n| `settings` | Settings | Settings |\n\nNav: `popUpTo(startDestination)`, `launchSingleTop`, `saveState` / `restoreState`.\n\n**Stack-only routes:**\n\n| Route | Screen | How reached |\n|---|---|---|\n| `a11y` | Accessibility explainer | Settings → Digital Wellbeing |\n| `vault` | Habit tracker | 5 taps on “Settings” title |\n\nOn setup complete: `UsageMonitor.checkAndNudge` + revision notify if due.\n\n---\n\n### 6.1 Interface A — One-Time Study Plan Builder (Setup) **[Shipped]**\n\n**Trigger:** First launch, or Settings → “Full setup again” (`resetSetup`).\n\n**Branding:** Logo avatar, headline **Nigakoi**, tagline: *OCR / paste / manual topics → local plan. LLM structures syllabus only.*\n\n#### Inputs\n\n| Field | Default / notes |\n|---|---|\n| Your name | `\"\"` |\n| About you | Free text (exam, struggle…) |\n| Best study hours | `\"21:00-24:00\"` |\n| LLM API key | Prefills from `LlmDefaults` when present; stored only in KeyStore on save |\n| Base URL | `https://token-plan-sgp.xiaomimimo.com/v1` |\n| Model | `mimo-v2.5-pro` |\n| Deadline | Today + 3 months (`yyyy-MM-dd`) |\n| Goal | `\"Finish full syllabus before exam\"` |\n| Syllabus text | OCR append and/or paste |\n| Weak topics | Comma-separated → substring match → `priority = high` |\n| YouTube channels | One per line → `followed_content.youtube_channels` |\n| Websites / RSS | One per line → `followed_content.websites` |\n| Use LLM to structure | Switch, default **on** |\n| Manual subject / topic | Appends `# Subject` + topic lines to draft |\n\n**Personal on first save:** tone hardcoded `casual_bangla_english` (full tone picker lives in Settings). \n**Default distraction apps:** `facebook`, `instagram`, `tiktok`, `chrome_facebook`.\n\n#### Syllabus format (local parser)\n\n```\n# Math\nAlgebra\nCalculus\n# Bangla\nGrammar\n```\n\n- Lines starting with `#` = subject headers\n- Other non-empty lines = topics (leading `-` / `*` stripped)\n- Junk filter strips meta / page / chapter noise (`PlanBuilder.isJunkTopicName`)\n- Weak hints: case-insensitive substring → `high` priority\n\n#### OCR **[Shipped]**\n\n- Camera or gallery → ML Kit Latin text recognition (`ImageUtils.uriToOcrText`)\n- Denied camera → toast `\"Camera permission lagbe\"`\n- Scan path sets `source = scanned_syllabus`, `entry_method = ocr_scan` when LLM path applies\n\n#### Processing\n\n1. Local parse: `PlanBuilder.parseSyllabusLocal(syllabus, hints)`\n2. If LLM on + key + syllabus: **stream** `streamSyllabusToJson` → live “LLM thinking…” panel (reasoning chunks, display capped ~800 chars); fallback non-stream `parseSyllabusToJson`\n3. Apply: `PlanBuilder.applyLlmSyllabusJson` (falls back to local parse on bad JSON)\n4. **`daily_plan` cleared** — no LLM calendar generation in product flow\n5. High-priority topics without `next_revision_due` get `PerformanceEngine.nextRevisionDue`\n6. `setup_complete = true`; monthly log: `\"Plan built (N topics)\"` (+ `\" via LLM\"` when used)\n\n#### Validation\n\n- **Review & save** enabled when `(syllabus.isNotBlank() || draftPreview.isNotEmpty()) && deadline.isNotBlank()`\n- Confirm dialog: parsed subjects / topics (preview capped ~12 topics per subject)\n- **Test LLM:** ping → success banner with model reply\n\n#### Offline path\n\nLLM toggle off → local parse only; status `\"Plan ready (offline).\"`\n\n#### Deferred from v1.2 (setup)\n\n- Editable drag-drop draft calendar UI\n- YouTube playlist suggestions at setup\n- PDF / notes resource uploads as first-class setup inputs\n- Nested subtopic tree UI (schema field exists; UI treats flat topics)\n\n---\n\n### 6.2 Interface B — Daily Dashboard **[Shipped]**\n\n**Screen:** `DailyScreen`. Title **\"Aaj\"** for today, **\"Daily\"** for past days.\n\n#### Header chrome\n\n| Element | Behavior |\n|---|---|\n| Streak flame | `🔥 {current_streak_days}` — infinite pulse (§6.9) |\n| FocusStreakCard | Phone-down completion history summary |\n| Phone-Down CTA | `\"📵 Start 25min Phone-Down Focus\"` → `PhoneDownDialog` |\n\n#### Date navigation\n\n- **WeekStrip:** Sunday-start week; green = studied, amber = planned; today amber border\n- Prev / next day; **future days disabled**\n- Past days: **PastDayRecord** read-only (no swipe / mark-done)\n\n#### Today-only blocks\n\n| Block | Behavior |\n|---|---|\n| Exam countdown | Days to `goal.deadline`; color <10 red, <30 amber, else green; alpha pulse if <10 |\n| All-done badge | `\"Aaj sব shesh!\"` + reopen Umma |\n| Distraction banner | If Usage Access + data: total min + worst app; red bg if total > 30 min |\n| Energy check-in | Chips `low` / `ok` / `high` → `energy_log` (also drives Pomodoro durations — §6.6) |\n| Coach phrase | `LocalCoach.dailyOpenMessage` (pooled variants); 👍/👎 → `nudge_feedback` (last 50) |\n| Distraction insight card | When `distraction_hotspots.size ≥ 3`: LLM `distractionInsight` or `LocalCoach.distractionInsight`; dismissible for today via `cached_phrases[\"distraction_insight_dismissed\"]` |\n| Focus picker | 1 topic if energy=low else 2; sort: due → high → shaky |\n| Revision due | Up to 8 topics |\n| Followed updates | `ContentTracker.fetchUpdates` on open |\n| Daily mode banner | Reflects `DailyMode` for the day |\n| Focus-choice UI | Mon/Thu: `\"Ajke kon ta?\"` — 2 ranked options → `pickTodayFocus` |\n| Today’s list | `SwipeTopicRow` with **why-this-now** secondary line; CHUNK labels `\"Bite i/n\"` |\n| Empty CTA | If monthly pool empty → instructions; else build pick from pool |\n| Pomodoro FAB | On focused / first topic when viewing today |\n\n#### Overlays (order)\n\n1. `DifficultyDialog` after mark-done (before Umma)\n2. `PomodoroDialog` / `PhoneDownDialog` on demand\n3. `UmmaDialog` when all today topics done (once per date)\n4. Variable-ratio **bonus** toast (occasional, on topic complete — §6.10) may accompany session-complete flash\n\n#### Today queue derivation (`TodayQueue`) **[Shipped]**\n\n> **`today_queue` is not stored.** Derived at read time.\n\n**Priority:** `daily_selected[date]` → ranked from `monthly_topics` → empty (CTA).\n\n| Constant | Value |\n|---|---|\n| `WEEKDAY_COUNT` | 2 |\n| `WEEKEND_COUNT` | 1 |\n\n| `DailyMode` | Days | Behavior |\n|---|---|---|\n| `FOCUS_CHOICE` | Mon, Thu | Wait for user pick; UI shows top 2 choices; queue empty until pick |\n| `CHUNK` | Tue, Fri | Small-bite framing; ≥2 slots; **cross-subject interleave** among equal-rank frontier (§6.10) |\n| `LIGHT` | Wed, Sat | Filter due / shaky / high; 1 slot |\n| `CATCH_UP` | Sun | If `pace_status == behind`: overdue+high; else soft light (1 slot) |\n\n**Ranking sort (desc):** due → high priority → shaky → weightage → status rank. \n**Tie-break:** date + `user_id` seeded shuffle **within fully tied groups only** (not global random). Stable for same day+user.\n\n**Why-this-now:** `TodayQueue.whyThisNow(topic)` — local string from `confidence` / `priority` / `weightage` / `next_revision_due` / `last_touched` / `status`. Shown under topic name on Daily (`SwipeTopicRow`) and Monthly pool. No LLM.\n\n---\n\n### 6.2b Monthly View **[Shipped]**\n\n**Screen:** `MonthlyScreen`.\n\n- Month navigation (current + past; future limited)\n- **MonthCalendarGrid:** heat by session count (0 / 1–2 / 3+); planned days amber; selected / today borders\n- **SelectedDayPanel:** planned topics + study sessions for tapped day\n- Past months: topics touched + activity / monthly log\n- Current month: per-subject progress cards (`LinearProgressIndicator`; thresholds 70% / 40% color)\n- **Monthly pool list:** tap toggles Today; long-press menu; overdue badge; **why-this-now** secondary line under topic name\n- Button: **Build today’s pick from pool**\n- Today’s manual selections summary (`AnimatedVisibility` when non-empty)\n\n---\n\n### 6.2c Topics View **[Shipped]**\n\n**Screen:** `TopicsScreen`.\n\n- Overall coverage banner (animated %)\n- Mini stats: Got it / Need review / Due / Subjects\n- Search + filters: `all`, `due`, `high`, `shaky`, `done`\n- Expandable **SubjectCard** per subject (chevron rotation + `animateContentSize`)\n- `SwipeTopicRow` + delete / monthly / daily context menu\n- **FAB:** Add Subject (name, OCR camera/gallery, topics text, weak topics) → `addSubjectFromText` (LLM if key present)\n\n**UI labels (storage keys unchanged):**\n\n| Storage | Label |\n|---|---|\n| `solid` | Got it |\n| `shaky` | Need review |\n| `unrated` | Not rated |\n| `high` priority | Important |\n\n---\n\n### 6.2d Overall Dashboard **[Shipped]**\n\n**Screen:** `OverallScreen`.\n\n- Animated coverage ring (1000 ms)\n- Streak flame pulse\n- Chips: Pace, Revise %, Got it, Need review\n- **Weekly micro-quest** (`WeeklyQuest.forWeek` — derived, not stored): title + progress bar\n- 4-week distraction sparkline (`weekly_distraction_trend`)\n- Digital Wellbeing Today: per-app minutes + Usage Access CTA\n- Hotspots (top 3 by `frequency_score`)\n- Topic avoidance (top 3)\n- Performance one-liner: LLM `performanceOneLiner` once/day → `cached_phrases[\"performance_oneliner\"]` (+ `_date`); fallback `LocalCoach.performanceSummary`\n- Profile card (name, about, hours, goal, deadline)\n- FocusStreakCard\n- Study History: last 14 days expandable; insight if `phone_minutes_before > 30`\n\n---\n\n### 6.3 Distraction Monitoring & Nudging **[Shipped]**\n\n#### UsageMonitor\n\nWatched packages mapped to labels: Facebook, Instagram, TikTok, Chrome, Twitter, Reddit, WhatsApp.\n\n| Rule | Value |\n|---|---|\n| Window | 20 minutes |\n| Min foreground time to nudge | ≥ 3 minutes |\n| Match | Label must match `personal.distraction_apps` (substring) |\n| Event retention | Last 200 `distraction_events` |\n| Hotspot bump | `frequency_score += 0.1` (cap 1.0); new hotspot starts 0.3 |\n| Avoidance bump | +0.1 on planned topic |\n| **Hotspot-aware cooldown** | After trigger rules pass: `UsageMonitor.nudgeCooldownMs` — **approaching** (hour before hotspot start) **3 min**; **in window** **5 min**; **outside** **20 min**; empty hotspots → **5 min** default. In-memory `lastNudgeAt` — no schema |\n\nMessage: LLM `nudge()` if key else `LocalCoach.distractionWarning` (pooled). \nNotification title: **Nigakoi**.\n\n#### DistractionAccessibilityService\n\n- Watches Chrome / Chrome beta/dev / Firefox / Facebook app\n- Triggers: `facebook_app`, `chrome_facebook`, `chrome_instagram`, `chrome_tiktok` (URL text scan depth ≤ 8)\n- **Debounce:** same hotspot-aware `nudgeCooldownMs` as UsageMonitor (was fixed 5 min)\n- Same profile updates as UsageMonitor\n- Fallback copy pattern: `\"{name}, {plannedName} waiting. Facebook pore — ek minute study e fire asho.\"`\n\n#### Privacy explainer **[Shipped]**\n\n`AccessibilityExplainerScreen` → system Accessibility settings. Manifest description states data stays on device.\n\n#### NudgeNotifier channels\n\n| Channel ID | Name | Importance | Notification ID |\n|---|---|---|---|\n| `nudges` | Study nudges | DEFAULT | 42 |\n| `revisions` | Revision reminders | DEFAULT | 43 |\n| `daily_target` | Daily target | HIGH | 44 |\n| `milestones` | Milestones | HIGH | 50 + pct |\n| `weekly_report` | Weekly report | DEFAULT | 60 |\n\n---\n\n### 6.4 Curated External Content Tracker **[Partial]**\n\n| Source | Behavior | Status |\n|---|---|---|\n| Websites | On-app-open RSS fetch; regex ``; max 8 distinct updates | **[Shipped]** |\n| YouTube | `ContentTracker` YouTube Data API path | **[Deferred]** — no API key field in UI; `ytApiKey` never passed → path never runs |\n| Cards | In-app list on Daily only — no algorithmic feed | **[Shipped]** for RSS |\n\n---\n\n### 6.5 Overall Performance Tracking **[Shipped]**\n\nNo quizzes. Performance = consistency + coverage + trend. Recomputed by `PerformanceEngine.recompute`.\n\n| Metric | Formula / rule |\n|---|---|\n| **Coverage %** | `(done * 100) / totalTopics` (integer) |\n| **Pace** | Window `[deadline − 90d, deadline]`; `expected = (elapsed / window) * total`; `ahead` if `done > expected + 2`; `behind` if `done < expected − 2`; else `on_track` |\n| **Streak** | Consecutive dates in `activity_log` ending today, with **once-a-week freeze** (§6.10) |\n| **Revision adherence** | Of topics with `next_revision_due ≤ today`, % touched on/before due+1 day |\n| **Weekly distraction trend** | 4 ints: weeks W−3…Now, each 7-day window event counts |\n| **Confidence trend** | Counts of `solid` / `shaky` across topics |\n| **Streak freeze week** | `performance.streak_freeze_week` — ISO week id when freeze already used |\n| **Last bonus date** | `performance.last_bonus_date` — cooldown gate for variable-ratio bonus |\n\n#### Streak freeze (canonical)\n\n`PerformanceEngine.streakDaysWithFreeze(activity_log, today, priorFreezeWeek)`:\n\n1. Walk backward from today while dates are in `activity_log` (same as plain streak).\n2. On first gap: if that gap day is in the **current ISO week**, the week-so-far has **≥1 study day** and **exactly one miss**, and freeze not already used this week → **skip the gap** (do not break streak) and persist `streak_freeze_week = ISO week id`.\n3. Two+ misses in the week → no freeze; streak stops at the gap.\n4. Plain `streakDays` (no freeze) kept for tests / call sites that need raw consecutive count.\n\n#### Revision intervals (canonical — Kotlin)\n\n| Condition | Days until next revision |\n|---|---|\n| `priority == high` | **3** (`REV_HIGH`) — wins over confidence |\n| `confidence == shaky` | **5** (`REV_SHAKY`) |\n| `confidence == solid` | **14** (`REV_SOLID`) |\n| else | **7** (`REV_DEFAULT`) |\n\n- `difficulty` (1–5) is **UI-only** — ignored by revision math\n- High priority does **not** auto-decay; user clears / changes priority manually\n- Marking done / confidence updates sets `last_touched` and recomputes `next_revision_due`\n\n> **Selfcheck drift:** `android/selfcheck.py` currently uses a slightly different interval table (e.g. high+shaky → 2). Kotlin `PerformanceEngine` is product truth; selfcheck should be aligned (§12).\n\nPresentation: Overall ring + chips + weekly micro-quest; Daily countdown / streak; performance one-liner (`performanceOneLiner` once/day cached, LocalCoach fallback).\n\n---\n\n### 6.6 Focus Tools & Celebration Dialogs **[Shipped]**\n\n#### PomodoroDialog\n\nDurations from today’s `energy_log` entry (`date == today`, most recent). Resolved once when dialog opens via `LocalCoach.pomodoroDurationsFor(level)` — mid-session energy change does **not** alter a running timer.\n\n| Today’s energy | Work | Break |\n|---|---|---|\n| `low` | 15 × 60 s | 3 × 60 s |\n| `ok` / no check-in yet | 25 × 60 s | 5 × 60 s |\n| `high` | 30 × 60 s | 5 × 60 s |\n\n- Circular progress arc / countdown use resolved `workSecs` / `breakSecs`; Start / Pause / Reset\n- **Energy label** (local string, no LLM): when low/high, one line above timer — *“15 min today — low energy”* / *“30 min — high energy”* (`LocalCoach.pomodoroEnergyLabel`)\n- **Pre-session hook** (before Start): if last `StudySession.phone_minutes_before > 0` → `LocalCoach.preSessionPhoneHook` line (*“Last time: N min phone before starting — beat it today”*) — independent of duration\n- Work complete → `onPomodoroWorkComplete(topicId, workMinutes)`: `topic.session_count++` **and** `logFocusSession` (feeds same-day momentum; toast only when ≥2 sessions today)\n- Close / dismiss → flush ceil(elapsed work secs) + completed work sessions into ViewModel `pendingPomodoro`; **swipe-done** consumes into `StudySession.duration_minutes` + `pomodoro_sessions` (still `0` if no Pomodoro that topic)\n- Does **not** auto mark topic done\n\n#### PhoneDownDialog\n\n| Constant | Value |\n|---|---|\n| Target | 25 × 60 s (fixed — distraction-resistance target, **not** energy-scaled) |\n\n- **Non-dismissible** (no back, no outside tap)\n- Breathing circle animation (§6.9)\n- Same **pre-session phone hook** for first ~15 s of timer when data exists\n- Copy: *Phone rakho, focus koro. Saans nao… in… out…*\n- Complete → `logFocusSession(25, completed=true)` — toast includes **momentum badge** when same-day completed `focus_sessions` ≥ 2 (`LocalCoach.momentumBadge`)\n- Quit → “I need to check my phone” → logs elapsed minutes, `completed=false`\n\n#### DifficultyDialog (inline Daily)\n\n- Prompt: “Eta kemon laglo?” / difficulty rate\n- Ratings 1–5: Easy, Soft, OK, Tough, Hard → `topic.difficulty`\n- Skip allowed; does not affect revision intervals\n\n#### UmmaDialog\n\n- Auto when all today topics done (after difficulty dismissed; once per date)\n- Romantic / flirty tones: 💋 “Umma! Aaj complete!”; else 🎉 “All done today!”\n- Message: `cached_phrases[\"daily_phrase\"]` or `LocalCoach.ummaMessage` (pooled, no-repeat-last-3)\n- Dismiss: “Muuuah 💋” or “Let’s go!”\n\n---\n\n### 6.10 Engagement & anti-boredom layer **[Shipped]**\n\nOffline-first. LLM upgrades copy when key present; LocalCoach / derived math always work. Does not change “LLM is disposable” principle. **No schema migration** for v2.3 additions.\n\n| Mechanism | Behavior |\n|---|---|\n| **Phrase pools** | `LocalCoach` triggers (`dailyOpenMessage`, `distractionWarning`, Umma, milestone, session complete, distraction insight, etc.) use **6–10** tone-aware variants per tone (`casual_bangla_english`, `strict_friend`, `romantic_lover`, `flirty`) |\n| **No-repeat-last-3** | `LocalCoach.PhrasePick` — in-memory rotating last-3 phrase IDs per bucket; **no schema bloat** |\n| **Light tie shuffle** | `TodayQueue.rankedTopics` — shuffle only within equal ranking keys; seed = `date.toEpochDay() xor user_id.hashCode` |\n| **Why this now** | `TodayQueue.whyThisNow` under topic name (Daily + Monthly pool) |\n| **Pre-session phone hook** | Last `phone_minutes_before` before Pomodoro / Phone-Down start |\n| **Energy-adaptive Pomodoro** | Today’s `energy_log` → work/break via `pomodoroDurationsFor` (§6.6); Phone-Down stays 25 min |\n| **CHUNK interleave** | `interleaveBySubject`: among equal-rank frontier, prefer subject ≠ last pick; never pull lower rank ahead of higher |\n| **Variable-ratio bonus** | ~**1-in-4** on topic complete (`maybeCompletionBonus`); **≥3 day** cooldown via `last_bonus_date`; toast may include ✨ + pulse (§6.9); stacks on top of Umma / milestone flows |\n| **Streak freeze** | Once per ISO week — exactly one miss bridged (§6.5) |\n| **performanceOneLiner** | Once/day on Overall/Daily `refreshLocalPhrases`; cache `performance_oneliner` + `_date`; prompt cites coverage/pace, strongest/weakest subject, one hotspot; LocalCoach fallback |\n| **distractionInsight** | Gate: `hotspots.size ≥ 3`; once/day cache; dismissible card on Daily (`distraction_insight_dismissed` = today); LocalCoach fallback |\n| **Momentum badge** | Read-time `LocalCoach.todayFocusCount` / `momentumBadge` — “2nd session today”, “3 in a row 🔥”, “N sessions today 🔥”; PhoneDown announce + Pomodoro work-complete (`announce=false` → badge-only when ≥2) |\n| **Pomodoro → StudySession duration** | Elapsed work flushed to pending map; `markDoneSwipe` writes real `duration_minutes` / `pomodoro_sessions` |\n| **Hotspot nudge gate** | Frequency/timing only (§6.3) — trigger rules unchanged |\n| **Weekly micro-quest** | `WeeklyQuest.forWeek` (derived like TodayQueue): shaky clear → high-priority touch → beat last week’s session count; Overall progress bar |\n\n### 6.7 Settings + Private Vault **[Shipped]**\n\n#### SettingsScreen\n\n**About You:** name, about, hours, tone chips (`casual_bangla_english`, `strict_friend`, `romantic_lover`, `flirty`), height (ft display → cm storage), weight (kg), Save.\n\n**LLM Config:** status indicator, API key, base URL, model, Save + Test.\n\n**Digital Wellbeing:** Usage Access + Accessibility Service enable rows.\n\n**Study Plan:**\n- “Clear auto plan (use manual picks)” → `rebuildPlan()` (clears `daily_plan`)\n- “Full setup again” → reset + navigate setup\n\n**Private (vault):** revealed after **5 taps** on “Settings” title (session counter) → navigate `vault`.\n\n#### HabitScreen (vault)\n\n- Isolated file `filesDir/h.json` — **not** in `UserProfile`; never in study LLM `trimCtx` / nudges / notifications\n- Today count +/− with bounce animation\n- Stats: clean streak days, weekly avg, best clean streak\n- 35-day heat grid vs `daily_cap` (default **1**, slider **1–10**)\n- **Week trend** line: this week total vs prior week (`HabitStore.weekTotal` / `prevWeekTotal` / `trendLabel`)\n- **Suggested limit** from history (`suggestDailyCap`: ceil(last-7 avg), ≤ peak-14d; if week DOWN → prefer peak−1) + **Apply** chip; user still owns slider\n- Default coach line: Banglish `LocalCoach.habitMessage` (PhrasePick pools by state × tone — vault-honest urge/self-control register; no medical advice / shame spirals)\n- Optional **Get Banglish LLM message** (`callLlm`) — payload includes streak/cap/avg/week totals/trend/suggested_cap; fail/no key → LocalCoach pool\n- Entries retained last 90 days; clean streak = consecutive days with count == 0 ending today\n- Unlock: 5 taps on Settings title (session) — still easter-egg, no public nav tab\n\n---\n\n### 6.8 Widget & Background Jobs **[Shipped]**\n\n#### StudyWidget\n\n- Min size ~180×110 dp; refresh ~30 min + on profile change\n- Shows: name, coverage %, streak, done today, pace, progress bar, message\n- Message: `widget_prefs.last_phrase` OR first 3 today topic names\n- `savePhrase()` exists but **not wired** from main app flow (§11)\n- Tap → MainActivity\n\n#### DailyCheckReceiver\n\n- Repeating alarm at study end hour parsed from `best_study_hours` (default **22:00**)\n- Daily target notification: LLM `dailyTargetMessage` or LocalCoach summary\n\n#### WeeklyReportWorker\n\n- WorkManager every **7 days**, initial delay until next Sunday\n- Aggregates: topics done, revisions, distractions, coverage, streak\n- LLM `weeklyReport` or LocalCoach weekly insight\n\n#### Boot\n\n- `RECEIVE_BOOT_COMPLETED` to reschedule alarms / workers as implemented\n\n#### Permissions (manifest)\n\n`INTERNET`, `RECEIVE_BOOT_COMPLETED`, `SCHEDULE_EXACT_ALARM`, `POST_NOTIFICATIONS`, `CAMERA`, `PACKAGE_USAGE_STATS` (+ Accessibility service declaration).\n\n---\n\n### 6.9 Motion & Animation Spec **[Shipped]**\n\nProduct requirement: motion creates presence and hierarchy — not noise. All values below match current Compose implementation.\n\n| Location | Trigger | Animation | Spec |\n|---|---|---|---|\n| **SwipeTopicRow** | Horizontal drag | Action-swipe (not dismiss) | Threshold **35%** width; `confirmValueChange = false` → spring back both ways |\n| **SwipeTopicRow** | Right swipe | Done + green flash | Reveal green bg `0xFF1A4D32` / text `0xFF4ADE80`; row bg `animateColorAsState` **280 ms** tween |\n| **SwipeTopicRow** | Left swipe | Undo | Amber reveal `0xFF3D2800` / `0xFFFFA040`; clears done flash |\n| **SwipeTopicRow** | Long-press | Context menu | Dropdown (delete / monthly / daily) — no enter animation required |\n| **Daily — streak flame** | Always when streak shown | Infinite scale pulse | Scale **1 → 1.25**, ~**600 ms**, reverse infinite |\n| **Daily — deadline** | Days left < 10 | Infinite alpha pulse | Soft blink on countdown text |\n| **Daily — distraction banner** | Usage data present | Color tween | Red vs surface via `animateColorAsState` when total > 30 min |\n| **Daily / Setup / Settings** | LLM success | `AnimatedVisibility` | Success banner appear/disappear |\n| **Monthly** | Today selections non-empty | `AnimatedVisibility` | Summary strip |\n| **Topics — coverage** | Coverage change | Progress / float | `animateFloatAsState`, **~600 ms** tween on bars |\n| **Topics — SubjectCard** | Expand / collapse | Size + chevron | `animateContentSize`; chevron rotation `animateFloatAsState` |\n| **Topics — filter list** | Expand subjects | `AnimatedVisibility` | Topic list enter/exit |\n| **Overall — coverage ring** | Coverage value | Arc sweep | `animateFloatAsState`, **1000 ms** tween; canvas sweep `360° * coverage` |\n| **Overall — flame** | Always | Infinite scale | ~**700 ms** pulse |\n| **Setup — thinking panel** | Streaming LLM | Size animate | `animateContentSize` while reasoning grows |\n| **PomodoroDialog** | Timer tick | Progress arc | `animateFloatAsState` on remaining/total |\n| **UmmaDialog** | Open | Scale-in | `Animatable` 0→1, **500 ms**, `FastOutSlowInEasing` |\n| **UmmaDialog** | Open | Pulse | Infinite scale pulse on emoji / card |\n| **UmmaDialog** | Romantic / flirty | Floating kiss text | Infinite float offset on 💕💋💕 |\n| **PhoneDownDialog** | Open | Breath circle | Infinite scale, **4 s** period (in/out) |\n| **HabitScreen — count** | Count change | Int + color + bounce | `animateIntAsState` **320 ms**; color tween; `Animatable` spring bounce (`DampingRatioMediumBouncy`) |\n| **HabitScreen — clean day** | Clean streak | Ring alpha pulse | Infinite soft pulse |\n| **HabitScreen — buttons** | Press | Scale | `animateFloatAsState` press scale |\n| **Settings — vault row** | 5 taps unlock | Fade | `fadeIn` / `fadeOut` via `AnimatedVisibility` |\n| **ToastBanner** | Show toast | Auto dismiss | **2500 ms** ( **3500 ms** if text contains `✨` bonus) |\n| **ToastBanner** | Bonus (✨) | Soft pulse | Infinite scale **1 → 1.03**, ~**450 ms**; warmer bg `0xFF3D2800` |\n\n**Accessibility / reduced motion:** not yet implemented — if added later, gate infinite pulses first (flame, breath, floating kiss).\n\n---\n\n## 7. LLM Usage Points\n\n**Transport:** `POST {base_url}/chat/completions` (OpenAI-compatible). \n**Defaults:** Xiaomi MiMo — base `https://token-plan-sgp.xiaomimimo.com/v1`, model `mimo-v2.5-pro`, provider `openai_compatible`. \n**Temps (approx):** chat 0.4, syllabus 0.2, ping 0.0.\n\n### Persona (`personaRule`)\n\n- `message_tone`: `affectionate_partner` (romantic / flirty) vs `friendly_casual` (strict / casual)\n- Bangla–English mix when preferences / tone imply Bangla\n- **`affectionate_partner` register:** girlfriend/partner study texts — soft adore + firm study push (“boi kholo”, “phone side-e”, “chapter sesh”, “study first, romance later”). 1–2 short sentences. Style-locked via few-shot vibe examples in `personaRule`\n- Hard ban: sexual / explicit content; random/off-topic; corporate pep talk; long paragraphs; emoji spam\n\n### Trimmed context\n\nAggregated JSON: personal, goal, performance, today’s IDs, behavior hotspots, weekly distraction counts, height/weight — **no raw distraction event timestamps**.\n\n### Call matrix\n\n| Method | Trigger | Frequency | Status |\n|---|---|---|---|\n| `parseSyllabusToJson` / `streamSyllabusToJson` | Setup / add subject | On demand | **Wired** |\n| `ping` | Test LLM | On demand | **Wired** |\n| `nudge` | UsageMonitor / Accessibility | On distraction (hotspot cooldown) | **Wired** |\n| `revisionReminder` | App launch if revisions due | On open | **Wired** |\n| `milestoneCelebration` | Coverage crosses 25 / 50 / 75 / 100% | Rare | **Wired** (fallback `LocalCoach.milestoneMessage`) |\n| `weeklyReport` | WeeklyReportWorker | Weekly | **Wired** |\n| `dailyTargetMessage` | DailyCheckReceiver | Daily | **Wired** |\n| `callLlm` (generic) | HabitScreen | On demand | **Wired** |\n| `performanceOneLiner` | Overall/Daily open (`refreshLocalPhrases`) | Once/day → `cached_phrases` | **Wired** (fallback `LocalCoach.performanceSummary`) |\n| `distractionInsight` | Daily when ≥3 hotspots | Once/day → `cached_phrases`; dismissible | **Wired** (fallback `LocalCoach.distractionInsight`) |\n| `dailyPhrase` | — | — | **Defined, not wired** (LocalCoach / Umma cache used) |\n| Plan / calendar LLM (`applyLlmPlanJson`) | — | — | **Disabled in product flow** |\n\n**Fallback pattern:** LocalCoach phrase pools first path or on failure; LLM upgrades copy when `apiKeyPresent()`.\n\n---\n\n## 8. User Profile — Persistent Data Schema\n\nSingle source of truth for UI + LLM context. Stored in Room (`nigakoi.db`, table `profile_doc`). One-time migrate from legacy `filesDir/user_profile.json` if present.\n\n### 8.1 Top-level `UserProfile`\n\n```json\n{\n \"user_id\": \"u_001\",\n \"preferences\": {\n \"language_tone\": \"bangla_english_mix_casual\",\n \"notification_style\": \"friendly_non_robotic\",\n \"daily_variation\": true,\n \"message_tone\": \"affectionate_partner\"\n },\n \"personal\": {\n \"display_name\": \"\",\n \"about_me\": \"\",\n \"tone\": \"casual_bangla_english\",\n \"best_study_hours\": \"21:00-24:00\",\n \"avoid_hours\": \"\",\n \"distraction_apps\": [\"facebook\", \"instagram\", \"tiktok\", \"chrome_facebook\"],\n \"height_cm\": 177,\n \"weight_kg\": 80\n },\n \"goal\": {\n \"type\": \"job_exam\",\n \"deadline\": \"2026-10-26\",\n \"target_description\": \"Finish full syllabus before exam\"\n },\n \"subjects\": [],\n \"monthly_log\": [],\n \"followed_content\": {\n \"youtube_channels\": [],\n \"websites\": []\n },\n \"behavior_profile\": {\n \"distraction_hotspots\": [],\n \"topic_avoidance_signals\": [],\n \"distraction_events\": [],\n \"preferred_study_hours\": []\n },\n \"performance\": {\n \"coverage_percent\": 0,\n \"pace_status\": \"on_track\",\n \"current_streak_days\": 0,\n \"revision_adherence_percent\": 100,\n \"weekly_distraction_trend\": [0, 0, 0, 0],\n \"confidence_trend\": { \"solid\": 0, \"shaky\": 0 },\n \"streak_freeze_week\": \"\",\n \"last_bonus_date\": \"\"\n },\n \"llm_config\": {\n \"provider\": \"openai_compatible\",\n \"api_key_status\": \"expired\",\n \"last_key_updated\": \"\",\n \"base_url\": \"https://token-plan-sgp.xiaomimimo.com/v1\",\n \"model\": \"mimo-v2.5-pro\"\n },\n \"daily_plan\": {},\n \"setup_complete\": false,\n \"activity_log\": [],\n \"energy_log\": [],\n \"nudge_feedback\": [],\n \"monthly_topics\": [],\n \"daily_selected\": {},\n \"cached_phrases\": {},\n \"study_sessions\": [],\n \"focus_sessions\": []\n}\n```\n\n### 8.2 Topic / Subject fields\n\n| Field | Values / notes |\n|---|---|\n| `status` | `not_started` \\| `in_progress` \\| `done` |\n| `priority` | `normal` \\| `high` |\n| `confidence` | `unrated` \\| `shaky` \\| `solid` |\n| `weightage` | Double? from syllabus marks; null if none; may inform ranking, never auto-sets priority |\n| `entry_method` | `ocr_scan` \\| `pasted_text` \\| `manual` |\n| `difficulty` | 1–5, default 3; UI only |\n| `session_count` | Pomodoro completions |\n| `subtopics[]` | Schema present; **UI deferred** (flat topics) |\n| `resources[]` | Schema present; limited UI |\n\nCaps: `distraction_events` last **200**; `preferred_study_hours` last **40**; `study_sessions` last **500**; `focus_sessions` last **300**; `nudge_feedback` last **50**.\n\n### 8.3 Session types\n\n**StudySession:** date, timestamp, topic_id/name, subject_name, duration_minutes, pomodoro_sessions, status_after, energy_level, phone_minutes_before.\n\n**FocusSession:** date, timestamp, duration_minutes, target_minutes (25), completed, optional topic. Written by Phone-Down **and** Pomodoro work-complete (v2.3 — shared same-day momentum count).\n\n**EnergyEntry** (`energy_log[]`): `date` (yyyy-MM-dd), `level` (`low` \\| `ok` \\| `high`). One entry per date (upsert). Feeds Daily focus-picker count + Pomodoro durations (§6.6).\n\n### 8.4 Derived / not stored\n\n- `today_queue` — computed via `TodayQueue` (tie shuffle + CHUNK interleave at read time)\n- `LocalCoach.PhrasePick` recent IDs — **in-memory only** (no-repeat-last-3)\n- API key — EncryptedSharedPreferences (`studycoach_secrets`) only\n- Habit vault — separate `h.json`\n\n### 8.5 HabitStore (`h.json`)\n\n```json\n{\n \"daily_cap\": 1,\n \"entries\": [{ \"date\": \"2026-07-27\", \"count\": 1 }]\n}\n```\n\nDerived at read time (not stored): `weekTotal`, `prevWeekTotal`, `trendLabel`, `suggestDailyCap`, `peakDayLast14`. Cap suggestion never raises above recent 14-day peak without user Apply/edit.\n\n---\n\n## 9. Tech Stack\n\n| Layer | Choice |\n|---|---|\n| Language | Kotlin 17 |\n| UI | Jetpack Compose + Material3 (BOM 2024.10.01) |\n| Navigation | Navigation Compose 2.8.3 |\n| DB | Room 2.6.1 (`nigakoi.db`) + kotlinx.serialization JSON document |\n| Networking | OkHttp 4.12.0 |\n| Secrets | `androidx.security:security-crypto` EncryptedSharedPreferences |\n| Background | WorkManager 2.9.0 + AlarmManager receiver |\n| OCR | ML Kit text-recognition 16.0.1 |\n| Widget | App widget (Glance-style update path in `StudyWidget`) |\n| Theme | Always dark — tokens in `Theme.kt` |\n\n### Theme tokens\n\n| Token | Hex |\n|---|---|\n| `DarkBg` | `#0F1914` |\n| `DarkSurface` | `#1A2620` |\n| `DarkSurface2` | `#223028` |\n| `GreenPrimary` | `#4ADE80` |\n| `GreenDim` | `#1B7A4E` |\n| `AmberAccent` | `#FFA040` |\n| `TextPrimary` | `#E8F5E9` |\n| `TextSecondary` | `#ADC5B0` |\n| `RedAlert` | `#FF6B6B` |\n\n---\n\n## 10. Decisions (resolved) & Remaining Open\n\n### Resolved by v0.1 implementation\n\n| Question (from v1.2) | Decision |\n|---|---|\n| High-priority auto-decay? | **No** — user clears manually |\n| Distraction retention? | Events capped at **200**; hotspots scored continuously |\n| Website / channel polling? | **On-app-open** RSS (not background poll); YT deferred |\n| Exact revision intervals? | high=3, shaky=5, solid=14, default=7; high wins |\n| Behavior data in LLM payload? | Hotspots + weekly counts yes; **raw event timestamps no** |\n| Auto daily calendar? | **Disabled** — monthly pool + manual / ranked daily picks |\n| Subtopics in UI? | Schema only for now |\n| Queue tie-break random? | **Light shuffle within ties only** (date+user seed); ranking fields stay deterministic |\n| Streak miss forgiveness? | **Once per ISO week**, exactly one miss, if week has ≥1 study day |\n\n### Still open\n\n- YouTube Data API key UI + polling cadence\n- Reduced-motion accessibility preference\n- Whether vault habit domain should stay easter-egg or become a first-class settings entry\n- Cloud backup format if/when §11 ships\n\n---\n\n## 11. Deferred / Future (not in v0.1 product commitment)\n\n| Item | Notes |\n|---|---|\n| Drag-edit draft plan UI | Rebuild from setup / settings instead |\n| Auto calendar (`PlanBuilder.buildCalendar` / LLM plan) | Code exists; product flow clears `daily_plan` |\n| YouTube Data API in UI | Tracker path exists; no key plumbing |\n| Nested subtopic UI | Schema ready |\n| PDF / notes resource library | Schema `resources[]` only |\n| Wire widget `savePhrase` from coach | Currently unused |\n| Wire LLM `dailyPhrase` | Umma still uses `cached_phrases[\"daily_phrase\"]` or LocalCoach; dedicated dailyPhrase call not wired |\n| Cloud backup / sync / multi-device | Explicit non-goal for v1 |\n| Light theme | Out of scope |\n\n> **Shipped in v2.3 (removed from deferred):** Pomodoro → `StudySession.duration_minutes`; wire `performanceOneLiner` / `distractionInsight`; momentum badge; hotspot nudge cooldown; `WeeklyQuest`.\n\n---\n\n## 12. Acceptance, Branding & QA\n\n### Branding checklist\n\n- Launcher / notifications / setup / widget: **Nigakoi**\n- DB name: `nigakoi.db`\n- UX language: Bangla–English mix where coach speaks\n- Tones: casual Bangla–English, strict friend, romantic lover, flirty\n- Daily completion ritual: **Umma** (romantic) or neutral celebration\n\n### Runnable checks\n\n```bash\n# from android/\npython3 selfcheck.py # no SDK — parser / coverage / calendar pad\n./gradlew test # PerformanceEngineTest etc.\n./gradlew assembleDebug\n```\n\n### Acceptance criteria (v0.1 + §6.10 + v2.2 + v2.3 + v2.4)\n\n1. Fresh install → Setup → offline or LLM plan → lands on Daily with `setup_complete`\n2. Add topics to monthly pool → Daily derives queue per `DailyMode` (or manual pick)\n3. Right swipe marks done → difficulty → all done → Umma once\n4. Left swipe undoes; undo stack restores status\n5. Usage Access / Accessibility → distraction event stored → notification (with or without LLM)\n6. Overall coverage / pace / streak match `PerformanceEngine` formulas (incl. freeze when eligible)\n7. API key survives in EncryptedSharedPreferences; profile survives key wipe\n8. Animations listed in §6.9 present on reference device (API 26+)\n9. Vault unlocks only after 5 Settings title taps; data in `h.json` not in profile export slice for LLM\n10. LocalCoach phrase pools vary; same bucket does not immediately re-show last 3 IDs\n11. Fully tied topics may reorder day-to-day; high priority still beats normal\n12. Why-this-now line visible under Daily + Monthly pool topic names\n13. Pomodoro / Phone-Down show last phone-before hook when prior session has `phone_minutes_before > 0`\n14. CHUNK day with equal-rank multi-subject pool prefers alternating subjects\n15. Bonus toast (~1/4, ≥3d cooldown) may fire on complete; streak freeze bridges exactly one weekly miss\n16. No energy check-in today → Pomodoro opens 25/5; low → 15/3 + label; high → 30/5 + label; mid-session energy change does not alter running timer; Phone-Down stays 25 min; `session_count` still increments on work-complete\n17. Pomodoro work then swipe-done → `StudySession.duration_minutes` > 0 (ceil elapsed work); swipe without Pomodoro still 0\n18. Overall/Daily open with key → `performance_oneliner` cached once/day; without key / fail → LocalCoach line still shows\n19. ≥3 distraction hotspots → Daily insight card; Dismiss hides for today; offline LocalCoach text works\n20. 2nd+ completed focus session same day → momentum toast (“2nd session today” / “3 in a row 🔥”)\n21. Outside hotspot windows → longer nudge cooldown; hour before hotspot → shorter (3 min); trigger mins≥3 unchanged\n22. Overall shows weekly micro-quest with progress (shaky / high-priority / beat last week)\n23. Vault: Banglish LocalCoach habit line shows without API key; PhrasePick varies by state\n24. Vault: suggested daily cap from history + Apply; week trend line visible; `h.json` never in study trimCtx\n25. Vault LLM button uses Banglish prompt + history payload; fail/no key falls back to LocalCoach\n\n### Known doc / test debt\n\n- Align `selfcheck.py` revision intervals with Kotlin `REV_*` constants\n- README still says OCR skipped — **incorrect**; OCR is shipped (update when touching README)\n\n---\n\n## Appendix A — DailyMode quick reference\n\n```\nMon/Thu FOCUS_CHOICE → user picks 1 of top 2\nTue/Fri CHUNK → ≥2 bites; interleave subjects within equal rank\nWed/Sat LIGHT → 1 due/shaky/high\nSun CATCH_UP → behind? overdue/high : soft 1\n\nRank: due → high → shaky → weightage → status\nTies: date+user seeded shuffle within group only\n```\n\n## Appendix B — Status legend used in this PRD\n\n| Tag | Meaning |\n|---|---|\n| **[Shipped]** | In `versionName` 0.1.0 source (incl. §6.10 + v2.3 wiring + v2.4 vault habit) |\n| **[Partial]** | Code path exists; blocked by missing UI / key / wiring |\n| **[Deferred]** | Intentional non-ship for v0.1; tracked in §11 |\n\n---\n\n*End of PRD v2.4 — Nigakoi / StudyCoach.*\n"}}}}