Spaces:
Sleeping
Sleeping
| # 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` | |