# 12 — Legacy Reference Map (`E:\Sites\healthdebug`)

> **Status:** REFERENCE ONLY. The greenfield build carries **no code** forward from this tree (per `00-MASTER-PLAN.md`). Fady has cleared referencing *anything that matches the plan* from the old code. This document maps where things live so rebuild work can consult concrete precedents instead of re-deriving them.
>
> Legacy root: `E:\Sites\healthdebug` · Go 1.26.4 / togo v0.21.0 · module `github.com/fadymondy/healthdebug` · live at health.fadymondy.com. The `.ref/` subfolder holds 4 further reference checkouts (togo framework, the pre-rebuild Apple-only app, sibling projects dr/rtx).

## 1. Top-level layout

| Path | What it is |
|---|---|
| `cmd/` | Entrypoints: `cmd/api/main.go` (serve + OpenAPI export), `cmd/migrate`, `cmd/seed` |
| `internal/` | Go backend (see §2) |
| `db/atlas/migrations/` | 13 hand-written Atlas migrations + `atlas.sum` |
| `api/` | `openapi.yaml` / `openapi.json` — the committed contract 3 clients generate from |
| `web/` | Vite 6 + React 19 + TanStack Router/Query SPA |
| `ios/` | SwiftUI: iOS + watchOS + macOS + WidgetKit + CarPlay |
| `android/` | Kotlin/Compose: `app/`, `core-api/` (generated client), `wear/` |
| `desktop/` | Electron tray app (loads the live web app) |
| `extension/` | Chrome MV3 extension |
| `docs/` | `BUSINESS.md`, `architecture.md`, `sync.md`, `deployment.md` (see §7) |
| `scripts/` | Codegen pipeline (`gen-rest.mjs`, `gen-graphql.mjs`, `pg-sync.mjs`, `check-i18n.mjs`), Windows ops, backup |
| `togo.resources.yaml` | 24.6 KB resource manifest (20 resources) driving schema/query generation |

No CI workflows exist (`.github/` has only templates).

## 2. Backend — where the old engines and rules live

**`internal/protocols/`** — the engines as pure functions (no I/O, injected clock). `internal/protocols/*_test.go` **is the executable protocol specification** — the new build's engine plugins should be validated against those cases first.

Exact legacy constants (valuable precedents for the greenfield spec's open questions):

| Engine (legacy file) | Constants as implemented |
|---|---|
| Hydration (`hydration.go`) | `GlassMl=250`, `MaxDailyMl=5000`, `LogCooldown=30s`; default goal 2500ml; expected intake paced **linearly across the work window** (not the whole day); statuses on_track / slightly_behind (deficit≤500) / dehydrated / goal_reached |
| Caffeine (`caffeine.go`) | `CaffeineBlockMinutes=90`; wake inferred = `work_start − 1h` (never asked); `MaxDailyCaffeineLogs=8`, `CaffeineLogCooldown=1min`; sugar-based = red bull/monster/energy drink/soda/cola; clean = cold brew/matcha/green tea/espresso/black coffee/americano; unknown kinds treated as clean |
| GERD (`shutdown.go`) | window = `shutdown_window_hours` before target sleep, **default 4h**; allowed = water/chamomile/anise; high-risk keywords list (spicy, fried, dairy, chocolate, citrus, tomato, coffee, soda, alcohol, mint); past sleep time rolls to tomorrow |
| Pomodoro (`pomodoro.go`) | work 25min / short break 5min / long break 15min / long break every 4 cycles / daily target 8 — **a direct legacy precedent for the greenfield's open "interval lengths" question** |
| Trigger families (`nutrition.go`) | `ibs_gerd`: whole eggs, falafel, deep fried, raw onion, raw garlic, cheddar, yellow cheese; `gout`: red meat, liver, duck, beans, lentils, legumes; `fatty_liver`: refined sugar, honey, nutella, jam, white flour, mixed carbs; safe lists for proteins/carbs/fats; lowercased substring matching (errs toward flagging) — **seed data for the greenfield food catalog** |
| Medications (`medications.go`) | `GraceMinutes=60`; schedules fixed_times / interval / as_needed; overdue is advisory only; skipped counts as kept; adherence with 0 decided = 100% |
| Cycle (`cycle.go`) | `MinCyclesForPrediction=3`, `IrregularThresholdDays=9` (refuse to predict); averages last 6 cycles; ovulation = next start −14d, fertile window −5/+1d; BBT confirmation = 3 readings ≥ +0.2°C over prior 6-day mean; Naegele +280d for pregnancy; `SafetyProvider` interface with `NoSafetyData{}` default — **no drug database wired up, by design** |
| Notices (`notify.go`) | caffeine-window-opened alert 20min, shutdown warning 30min; weigh-in only 07:00–11:00; one notice per kind per user per local calendar day |

**REST surface** (`internal/rest/`, Huma v2): `GET /api/today` returns the whole computed day (`TodayState`); `POST /api/today/{water|caffeine|meal|pomodoro}` (429 on cooldown/cap); `GET|PATCH /api/profile`, `/api/sleep_config`; idempotent partial `PUT /api/summaries/{date}` rollups; per-user CRUD collections (water_logs, meal_logs, caffeine_logs, pomodoro_sessions, notification_items, health_samples, devices, medications + nested schedules, medication_intakes, cycles, cycle_days, pregnancies); shared reference data (`/api/drinks`, `/api/food_items` + `/classify`, `/api/site`); `POST /api/ai/{insight|chat}`; `/api/app/version` update gate; admin surface; SSE at `GET /api/events`.

**Auth**: togo auth plugin — HttpOnly cookie + CSRF for web, personal access tokens for native (`POST /api/auth/tokens`, plaintext once, per-device revoke); deny-by-default guard (`internal/httpx/guard.go`) with a short method-scoped allowlist; owner only ever from the session identity; foreign rows 404 not 403; `isolation_test.go` proves it over real HTTP.

**Database conventions** (kept in the greenfield spec): every column NOT NULL with a default. Legacy-specific choices to **re-evaluate**: text ids (not uuid), absent dates as empty text, no FK to the auth-owned users table, triggers as CSV text. Notable defaults in `profiles` (46 cols): work 9→19, water goal 2500, timezone UTC, locale en; `daily_summaries` unique `(user_id, date)`; `pregnancies` partial unique one-ongoing-per-user; `drug_safety_facts` table exists with zero implementations (the deliberate empty seam).

## 3. Clients — state and precedents

| Surface | State | Notable for the rebuild |
|---|---|---|
| Web (`web/`) | Full SPA | TanStack Query as the only state layer + SSE-driven invalidation; design tokens in `web/src/app.css` (light `#016b93`/`#269761`, dark bg `200 45% 7%`, defaults to dark); hand-rolled i18n (~315 EN→AR pairs, English strings as keys); RTL via `dir` + Tailwind logical properties; IBM Plex Sans + Plex Sans Arabic self-hosted; drag-reorderable Today cards (8 card ids) |
| iOS (`ios/`) | Full app, 3 tabs, 9.8k lines | **Liability, do not repeat:** a complete *second* protocol implementation in `HealthDebugKit/Health/` (iOS never calls `/api/today`), plus a second identity system (Firebase remnants). Riches to mine: 11 local notification schedulers, App Intents/Siri, `FoodRegistry.swift` (origin of the Go trigger lists), HealthKit manager reading steps/energy/HR/weight/body-fat/sleep (Zepp scale arrives through Apple Health) |
| WidgetKit | **Most complete non-phone surface** | 11 home + 6 lock-screen widgets, interactive actions, App Group data store |
| watchOS | Working standalone app (463 lines) | 6 screens incl. AI score; no complications |
| Android (`android/`) | Full phone app, 7.3k lines | Generated API client from openapi.json; Health Connect with point-of-use permission groups (16 permissions, hydration/nutrition write-back); update gate; 225/225 en/ar string parity; **no notification implementation at all** (only an unread badge) — confirms Divergence #3 |
| Wear OS | Shell only (136 lines) | Token push from phone doesn't exist |
| Electron (`desktop/`) | Working scaffold (424 lines) | Loads live web app; tray, global shortcut Ctrl/Cmd+Shift+W, idle detection, PAT in `safeStorage`, electron-updater |
| Chrome ext (`extension/`) | Working MV3 (428 lines) | Minimal permissions (storage/alarms/notifications + one host, **no content scripts**); chrome.alarms for everything; token in `chrome.storage.session`; badge = hydration % / focus minutes / red `!` on violation; `_locales/{en,ar}` |

## 4. Cross-cutting precedents

- **Notifications:** pure rules (`notify.go`) → in-process 15-min sweeper (`internal/notifier/`) → `notification_items` rows with `vars` JSON beside the sentence (never interpolated — Arabic reorders). Routing (`internal/realtime/route.go`): live SSE watcher suppresses push; else most-recent device (12h window) → primary → none, every decision carrying a `Reason`. **Delivery never shipped** — no FCM/APNs tokens registered.
- **Realtime:** per-user SSE hub replacing togo's broker (which leaked events across users). Events carry resource names, no state — clients refetch.
- **AI:** server-side insight/chat (`internal/ai/`), domains validated not trusted, `DayContext` flattened struct (only token-worthy fields), system prompt forbids diagnosis/doses/inferring absent numbers, 2–3 sentence cap, honest local fallback with no key. iOS additionally ran its own on-device AI stack — another duplication to not repeat.
- **i18n:** three independent hand-maintained systems (web dict / Android XML / iOS String Catalog) + extension locales; backend emits English source strings + vars. The greenfield's single ICU-based pipeline replaces all of this.
- **Gamification: none exists** in the legacy tree (exhaustively searched) — greenfield future-state starts from zero.

## 5. What the legacy answers for the greenfield's open questions

1. **Pomodoro intervals** — legacy precedent: 25/5/15, long break every 4, daily target 8 (open question in `03-protocol-engines.md`; needs ratification, plus the *kinetic* wearable-validation the legacy never had).
2. **Trigger-family catalog seed** — the legacy lists in `nutrition.go` / `FoodRegistry.swift` plus the `food_items` + `drinks` tables (with `name_ar`) are a ready seed dataset with admin CRUD precedent.
3. **Hydration window** — legacy paces the goal across the **work window**, not the calendar day, and caps at 5000ml/day; informs the day-boundary/anchor open question.
4. **Wake anchor** — legacy inferred wake = work_start − 1h rather than asking; a candidate answer for the Caffeine Block anchor question.
5. **Offline/no-wearable pomodoro** — legacy kept countdowns on-device and only counted completed work sessions; greenfield's kinetic validation is new territory.
6. **Push transport** — legacy built routing but never registered FCM/APNs; the greenfield notification plan must include actual delivery from day one.
7. **Update gate** — `app_releases` + public `GET /api/app/version` (server-owned, unauthenticated by necessity) is a proven pattern worth carrying into the plan.

## 6. Liabilities the greenfield explicitly avoids

1. **Duplicated protocol logic on iOS** (full second implementation; iOS never called the API) — the #1 correctness liability; the greenfield thin-client rule exists because of this.
2. **Two identity systems on iOS** (Firebase + server PATs).
3. **Design tokens duplicated by hand in three files** (`app.css`, `Theme.kt`, `Theme.swift`) with no shared source — greenfield uses a single token source (PENDING DESIGN).
4. **Three hand-maintained i18n systems** — replaced by one ICU pipeline.
5. **Shells shipped as features** (Wear OS without token transport; push routing without delivery) — greenfield phase exit criteria require end-to-end function.

## 7. Keys & configuration

Credentials, service configs, and environment values extracted from the legacy tree are stored in the **cabrain secrets vault** (`healthdebug` brain) rather than in this document. See the memory entries under source_ref `greenfield-spec-2026-09-01-*` for the inventory.
