Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/axif0_study-plan.json
T

1 line
46 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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 &lt;10 red, &lt;30 amber, else green; alpha pulse if &lt;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 &gt; 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 `<title>`; 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 &lt; 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 &gt; 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"}}}}