# Habit Journal — Architecture Single-user PWA on a Hugging Face Docker Space: **FastAPI** backend + **Vite/Preact** frontend, JSON/JSONL under `DATA_ROOT`, server-owned stats. Optional OpenRouter for coach/reschedule with Rules backup. Interactive canvas (Cursor): `habit-journal-architecture.canvas.tsx` --- ## Stack at a glance | Layer | Technology | |-------|------------| | Host | Hugging Face Docker Space | | Server | Uvicorn + FastAPI (`app.main:create_app`) | | Static UI | Vite → `static/` (PWA shell + service worker) | | Data | JSON/JSONL under `DATA_ROOT` (default `/data/loop_logger`) | | Optional LLM | OpenRouter (coach + reschedule); Rules backup if missing | **One line:** HF Docker → Uvicorn/FastAPI → JSONL stores → Vite/Preact PWA (`credentials: 'include'`) → optional OpenRouter with Rules backup. --- ## System overview ```mermaid flowchart TB subgraph clients [Clients] Browser[Browser PWA] Agent[Cursor agent] end subgraph space [HF Docker Space] Static[static/ Vite build] API[FastAPI create_app] SW[Service Worker shell-only] end subgraph data [DATA_ROOT] Config[config.json] Entries[entries.jsonl] Daily[daily.jsonl] Traces[traces.jsonl] Sched[schedule/] end OR[OpenRouter optional] Browser --> Static Browser --> API Browser --> SW Agent -->|Bearer AGENT_TOKEN| API API --> Config API --> Entries API --> Daily API --> Traces API --> Sched API -.->|coach / reschedule| OR ``` Notes: - Browser loads the static shell; **`/api/*` is network-only** (SW must never cache API). - Agent uses `Authorization: Bearer AGENT_TOKEN` on `/api/agent/*` (session login also works as fallback). --- ## Backend layers ```mermaid flowchart LR subgraph edge [HTTP] RAuth[auth] REnt[entries] RDaily[daily] RPlan[plan] RAgent[agent] RCoach[coach] RStats[stats] RDbg[debug/export/settings/health] end subgraph services [Services] CoachSvc[CoachService] Resched[RescheduleService] Evidence[evidence + stats_math] SchedMath[schedule_math] end subgraph stores [Stores] EntryStore DailyStore ScheduleStore TraceStore ConfigStore end RAuth --> ConfigStore REnt --> EntryStore RDaily --> DailyStore RPlan --> ScheduleStore RPlan --> Resched RAgent --> ScheduleStore RAgent --> DailyStore RAgent --> EntryStore RCoach --> CoachSvc CoachSvc --> Evidence CoachSvc --> TraceStore RStats --> Evidence Resched --> SchedMath ScheduleStore --> SchedMath ``` ### Module map | Module | Responsibility | |--------|----------------| | `app/routers/*` | HTTP edge; envelope `{ok, data, error}` | | `app/store_*` | JSONL persistence + file locks | | `app/stats_math.py`, `app/evidence.py` | Server-owned ranks & `SERVER_PICKS` | | `app/coach_service.py`, `app/backup_replies.py` | OpenRouter or Rules backup | | `app/schedule_*` | Plan math, locked EV, reschedule | | `app/deps.py`, `app/security_passwords.py` | Session + AGENT_TOKEN bearer | ### API surface | Group | Endpoints | |-------|-----------| | Health | `GET /api/health`, `GET /api/ready` | | Auth | `POST` login / logout / change-password, `GET /api/auth/me` | | Entries | CRUD `/api/entries` + filters | | Daily | `PUT`/`GET /api/daily/{day}`, `GET /api/daily?start=&end=` | | Plan | Plan CRUD, feedback, seed, reschedule, priors, templates, health | | Agent | `GET /api/agent/context`, `PUT /api/agent/plan/{day}` | | Coach | `POST /api/coach`, `POST /api/entries/{id}/coach` | | Stats | `GET /api/stats`, `GET /api/stats/remedies` | | Debug | Traces, paste bundles | | Export | Entries/daily JSONL + CSV | | Settings | `GET /api/settings/status` (no secrets) | --- ## Frontend UI ### Nav IA `Home | Plan | (+) Log | Daily | Settings` History remains at `#/history` (reachable from Home “See all”). Center FAB stays **Log** (one tap). ```mermaid flowchart TB Login["#/login"] --> AuthGate{session?} AuthGate -->|yes| Shell[App shell] Shell --> TabBar subgraph tabs [Bottom nav] Home["#/ Home"] Plan["#/plan Plan"] FAB["(+) Log"] Daily["#/daily Daily"] Settings["#/settings"] end TabBar --> Home TabBar --> Plan TabBar --> FAB TabBar --> Daily TabBar --> Settings Home --> History["#/history"] History --> Entry["#/entry/:id"] Home --> Coach["#/coach"] Home --> Stats["#/stats"] Settings --> Remedies["#/remedies"] FAB --> Log["#/log"] Log -->|high FSE| ProofSheet[Proof brick sheet] ProofSheet --> DailyAPI[PUT /api/daily] Plan --> BlockSheet[Block feedback sheet] ``` ### Routes | Route | Page | Role | |-------|------|------| | `#/` | Home | Week score, pending, plan next, indoors/proof nudges | | `#/plan`, `#/plan/:date` | Plan | Day timeline, seed, reschedule, block feedback | | `#/log` | Log | Fast entry + high-FSE proof wizard | | `#/daily` | Daily | 6-pt scoreboard + movement / fantasy / proof fields | | `#/settings` | Settings | Status, password, export | | `#/history` | History | Entry list (via Home) | | `#/entry/:id` | Entry | Detail + coach | | `#/coach` | Coach | Free-text coach + SERVER_PICKS | | `#/stats` | Stats | Server-owned `p_helped` / ranks | | `#/remedies` | Remedies | Remedy leaderboard | | `#/login` | Login | Password → session cookie | ### UI stack - Vite + Preact + TypeScript - Motion (press / sheets) - lucide-preact icons - Plus Jakarta Sans, warm journal UI (`#EBE7E0`) - `credentials: 'include'` on all API calls; `401` → `#/login` ### Key components | Component | Role | |-----------|------| | `TabBar` | Home \| Plan \| (+) Log \| Daily \| Settings | | `Timeline` + `BlockSheet` | Plan day + thick feedback | | `ResolvePendingSheet` | Pending resolve in < 3 taps | | `Toast` / `Sheet` / `Pressable` | Shared chrome | | `CoachResult` | Rules \| Model badge | --- ## Auth ```mermaid sequenceDiagram participant U as User participant UI as PWA participant API as FastAPI participant Disk as config.json U->>UI: password UI->>API: POST /api/auth/login API->>Disk: verify PBKDF2 hash API-->>UI: Set-Cookie session UI->>API: GET /api/* credentials include Note over API: 401 → redirect #/login participant A as Agent A->>API: GET /api/agent/context Note over A,API: Authorization Bearer AGENT_TOKEN ``` - **Human:** PBKDF2 password in `config.json` → signed session cookie (`sv` + `exp`). - **Agent:** Bearer `AGENT_TOKEN` on `/api/agent/*` (or session). - **Envelope:** `{ ok, data, error: { code, message } }`. - Server owns points, `p_helped`, ranks, `d_hat` — LLM must not invent %. --- ## Coach pipeline ```mermaid flowchart TB Req[POST /api/coach] --> Evidence[build_evidence SERVER_PICKS] Evidence --> Key{OPENROUTER_API_KEY?} Key -->|no / force_backup| Rules[backup_replies] Key -->|yes| OR[OpenRouter] OR -->|parse fail / label at high I| Rules Rules -->|I ge 7| HighFSE[high_intensity_fse] OR --> Trace[traces.jsonl] Rules --> Trace Trace --> UI[CoachResult Rules or Model] ``` At **intensity ≥ 7**, Rules prefer interruptive remedies (leave room, interrupt walk without cinematic headphones, one proof line) — not “Labeling” alone. --- ## Schedule / Plan ```mermaid flowchart TB AgentPull[GET agent/context] --> Caps[capacity_hint + operators] Caps --> Put[PUT agent/plan or UI seed] Put --> PlanFile[plan-YYYY-MM-DD.json] UI[Timeline + BlockSheet] --> FB[POST feedback] FB --> Feedback[feedback.jsonl] Feedback --> Priors[priors.json d_hat] UI --> Resched[POST reschedule] Resched -->|OR or rules| PlanFile ``` **P1 scope lock:** thin loop only (timeline + thick feedback + priors + capacity cuts). EV formula locked; no PERMA / δ / H in code. See [SCHEDULE.md](SCHEDULE.md). --- ## Domain: loop-break / movement ```mermaid flowchart LR Log[Log entry] -->|intensity ge 7| Proof[Proof wizard] Proof --> DailyRow[Daily proof_brick_*] DailyRow --> Home[Home banners] DailyRow --> AgentCtx[agent/context movement] Walk[Interrupt vs fantasy walk] --> DailyRow Coach[Coach Rules] -->|intensity ge 7| Interrupt[Interrupt not Labeling] PlanTpl[fantasy_slot_25 template] --> Plan[Day plan] ``` Daily additive fields include: `stayed_indoors_all_day`, `left_room`, `left_home`, walk splits, headphones / cinematic flags, `proof_brick_*`, fantasy scheduled vs unplanned minutes. **6-pt Daily formula unchanged.** --- ## Persistence layout Under `DATA_ROOT` (default `/data/loop_logger`): | Path | Purpose | |------|---------| | `config.json` | Password hash, session version | | `entries.jsonl` | Log entries | | `daily.jsonl` | Scoreboard + movement / proof / fantasy | | `traces.jsonl` | Coach debug traces | | `coach_brief.md` | Coach brief | | `schedule/plan-YYYY-MM-DD.json` | Versioned day plan | | `schedule/feedback.jsonl` | Block thick feedback | | `schedule/priors.json` | `d_hat` / `p_done` / fun priors | | `schedule/meta.json` | Reschedule cooldown | Path definitions live in [`app/paths.py`](../app/paths.py). --- ## Related docs - [BACKEND.md](BACKEND.md) — API / storage / coach / ops - [FRONTEND.md](FRONTEND.md) — PWA UI contract - [SCHEDULE.md](SCHEDULE.md) — Plan / agent / P1 scope lock ## Local tooling - `scripts/pull_handoff_bundle.py` — pull live Space state into a paste bundle (`handoff/` gitignored) - `.env` — local secrets only (gitignored): `HF_TOKEN`, `HF_SPACE_URL`, `APP_PASSWORD`, `AGENT_TOKEN`