الصفحات · Health Debug
Phase 2 — React + TanStack Web Dashboard Plan
Project: Health Debug (healthdebug.com) — greenfield build Phase: 2 of 6 (follows Phase 1: ToGO backend + PostgreSQL + auth) Status: PLANNING ONLY — no implementation. Visual/UI design is being finalized separately; every item that depends on it is flagged PENDING DESIGN inline.
Purpose
This document plans the React + TanStack web dashboard: the first client of the ToGO REST API. It defines the proposed project structure, the routing and server-state architecture (TanStack Router + TanStack Query), the theming engine (Dark Mode "Glowing Effects" vs Light Mode "Flat Colors" via design tokens), the i18n foundation (ICU Message Format, en/ar) with a full RTL strategy, and a component inventory sketch. The web dashboard is a presentation layer only: all protocol state — timers, windows, cooldowns, engine states — is fetched from the ToGO API, never computed or hardcoded locally. Components planned here will be reused by the Electron desktop client in Phase 6, so shareability is a first-class structural concern.
1. Non-Negotiable Constraints (inherited from the platform spec)
These shape every decision below:
- SSOT rule. The ToGO REST API is the Single Source of Truth for all temporal and state logic. The web client NEVER computes or hardcodes protocol business logic (no local "is caffeine allowed yet?" checks, no client-side food categorization, no local cooldown enforcement). The client renders server state and collects input.
- Categorical, not quantitative. UI never presents food as quantities to be scored — food is 'Safe' or 'Trigger-bearing' as returned by the backend Trigger Families engine. No client-side deduction UI.
- No invented medical certainty. Any AI-derived surface (insights, pattern summaries) renders backend output verbatim and always carries the "Consult your doctor" deferral. The web client adds no interpretation of its own.
- BYOK security. No AI provider API keys ever reach the client. All AI calls are proxied server-side. The web app may collect a key in a settings form and submit it to the backend for encrypted storage — it never stores, echoes, or uses the key locally.
- Native-only client policy. The web app is the React client; it must not attempt to be a hybrid wrapper for other platforms. (Electron in Phase 6 reuses web components but is its own client.)
What "no local logic" means for timers (the hard edge case)
Several engines are time-based (Hydration 30-second cooldown, Caffeine 90-minute block, GERD 4-hour window, Medication 60-minute grace, Kinetic Pomodoro). The line we draw:
- The backend owns state, deadlines, and validity. API responses carry the engine state plus server-computed timestamps/deadlines (e.g., "caffeine block active until T").
- The client may animate a countdown toward a server-provided deadline purely as a display concern, but it never decides anything from that animation. Every action (log water, log caffeine, log food, log medication) is submitted to the API, and the API's accept/reject response is the only truth. If the client's animated countdown and the server disagree (clock skew), the server response wins and the UI re-syncs via query invalidation.
- Exact response shapes for deadlines/state are owned by the Phase 1 backend plan and are not defined here — this plan only requires that they exist.
2. Proposed Project Structure
Stack (fixed by spec): React, TanStack Router (routing), TanStack Query (server state). Stack (proposed, not mandated by spec — open decision): Vite as build tool, TypeScript, Vitest + Testing Library for tests, a CSS-variables-first styling approach (see §4). These are proposals to be confirmed, not spec requirements.
To serve the Phase 6 Electron reuse requirement, the structure separates shared, platform-agnostic UI/logic from web-shell concerns from day one. Proposal: a lightweight monorepo workspace (exact workspace tooling — pnpm/npm workspaces/turborepo — is an open decision):
Sharing rules for packages/ui:
- No
window/browser-API access inside shared components; anything platform-specific goes through an injected adapter fromapps/*/src/platform/(e.g., notification permission, external-link opening — the two known divergence points with Electron). - No fetching inside presentational components; data arrives via props or via hooks from
packages/api-clientso Electron reuses the identical data layer against the same ToGO API. - No protocol constants (no
90,250,5000,30,240,60,3hardcoded as behavior). Display strings that mention these values come from the backend or from i18n messages whose values are interpolated from API data — never from client-side logic.
3. Routing & Server State
3.1 Route Tree (TanStack Router)
File-based routing. Proposed route map (labels/order PENDING DESIGN — information architecture below is a functional sketch, not final navigation):
Notes:
- Route-level
loaders prefetch queries via the shared QueryClient so navigation renders from cache. - Auth guard at the root layout route; session model comes from TOGO auth (Phase 1 owns token/session mechanics — undefined here by design).
- Future-state routes (doctor dashboard, B2B, marketplace, gamification badges) are explicitly out of Phase 2 scope; the route tree must not block adding them later, but nothing is built for them now.
3.2 Server State (TanStack Query)
All protocol state lives in TanStack Query caches keyed by a central queryKeys factory in packages/api-client:
| Query key (sketch) | Feeds | Invalidated by |
|---|---|---|
['engines', 'summary', date] | Dashboard engine cards, Human Node | any successful log mutation |
['engine', 'hydration', date] | Hydration ring, hydration detail | log-water mutation |
['engine', 'caffeine', date] | Caffeine block card/countdown | log-caffeine mutation, wake-time change |
['engine', 'gerd', date] | GERD window card | food/drink log mutation |
['engine', 'triggers', date] | Food log + Safe/Trigger verdicts | log-food mutation |
['engine', 'medication', date] | Medication grace card | log-medication mutation |
['engine', 'cycle'] | Cycle status/prediction (or "insufficient data") | cycle log mutation |
['engine', 'contraceptive'] | Contraceptive schedule status | contraceptive log mutation |
['engine', 'pomodoro', 'current'] | Pomodoro timer card | start/stop mutations; server push |
['timeline', date] | Today timeline | any log mutation |
['notifications'] | Notification center | mark-read mutations; server push |
Policies:
- Mutations are the only writers. Optimistic updates are allowed ONLY for pure-presentation concerns (e.g., disabling a button while in flight). We do not optimistically mark a hydration entry as accepted, because the 30-second cooldown and 5000ml cap are server-enforced — the UI must reflect the server's accept/reject, including rendering the server's rejection reason.
- Refetch cadence. Time-window engines need fresh state near their boundaries. Baseline:
refetchOnWindowFocus: true, plus targeted refetch when a server-provided deadline elapses. Whether the backend offers a push channel (SSE/WebSocket) for engine transitions and smart alerts is owned by Phase 1 and currently undefined — this plan works with polling/refocus alone and upgrades cleanly if push exists (open question §9). - Endpoint table. Final REST paths are owned by the Phase 1 backend plan. The table below is the consumption contract sketch from the web client's perspective only:
| Client need | Sketched endpoint (subject to Phase 1) | Verb |
|---|---|---|
| Engine summary for a date | /api/v1/engines/summary | GET |
| Log water (250ml unit) | /api/v1/hydration/entries | POST |
| Log caffeine / food / drink | /api/v1/intake/entries | POST |
| Log medication | /api/v1/medications/entries | POST |
| Cycle logs & prediction state | /api/v1/cycle | GET/POST |
| Contraceptive status/logs | /api/v1/contraceptive | GET/POST |
| Pomodoro session state | /api/v1/pomodoro/current | GET/POST |
| Notifications | /api/v1/notifications | GET/PATCH |
| BYOK key submission | /api/v1/ai/keys | POST (write-only from client) |
| Theme/locale profile prefs (server-persisted?) | undefined — open question §9 | — |
3.3 Data-flow diagram
4. Theming Engine
4.1 The two modes (fixed by spec)
| Token role | Dark Mode — "Glowing Effects" | Light Mode — "Flat Colors" |
|---|---|---|
| Background | #1A1A1A charcoal | #FFFFFF |
| Active timers / primary active | #00FF33 neon green with glow | #20A060 saturated teal, flat |
| Active buttons / interactive | #00CCFF cyan with glow | #0070A0 deep cyan blue, flat |
| Alerts | #FF3333 red with glow | alert color undefined in spec — PENDING DESIGN (spec defines only bg + two accents for light mode) |
| Effects | glowing (CSS shadows) | none — flat fills |
Additional neutrals (text colors, borders, surfaces, disabled states, chart gridlines) are PENDING DESIGN — the spec defines only the colors above; we will not invent intermediate shades in this plan.
4.2 Implementation plan: design tokens as CSS variables
- Single source:
packages/tokensdefines the palette as design tokens, emitted as CSS custom properties in two theme scopes (e.g.,[data-theme="dark"]/[data-theme="light"]on the root element). All components consume semantic tokens (--color-bg,--color-timer-active,--color-interactive,--color-alert,--glow-timer, …), never raw hex. - Glow is a token, not a component behavior. Dark theme defines glow tokens as CSS shadow values (
box-shadowfor shapes/buttons,text-shadowfor glowing text, SVGfilter: drop-shadow(...)for the hydration ring and Human Node strokes). Light theme defines the same tokens asnone. Components therefore render glowing in dark and flat in light with zero conditional logic. Exact glow radii, blur, spread, and layering: PENDING DESIGN. - Typography scale, spacing scale, radii, elevation, motion/animation timing: PENDING DESIGN — token slots will be reserved in
packages/tokens, values filled from the finalized design.
4.3 Mode selection: system preference + manual override
- Three-way setting: System / Dark / Light. Default: System, resolved via
prefers-color-schemewith a change listener so the app flips live when the OS does. - Manual override persists locally (e.g.,
localStorage) so first paint is correct with no flash-of-wrong-theme (inline pre-hydration script setsdata-themebefore React mounts). Whether the preference is also persisted server-side for cross-device sync is an open question (§9). - The theme provider lives in
apps/webbut the token definitions live inpackages/tokensso Electron (Phase 6) reuses them identically.
5. i18n Foundation
- Format: ICU Message Format for all user-facing strings — plurals, selects, and interpolation handled by ICU, string concatenation is banned (enforceable via lint rule — tooling choice open).
- Locales:
enandarcatalogs inpackages/i18n(e.g.,en.json/ar.json, one flat or namespaced catalog per locale — file layout to be finalized with library choice). - Library choice: undefined by spec — candidates are FormatJS/react-intl or i18next + ICU plugin. Open decision (§9). Requirement either way: full ICU support and message extraction tooling.
- Numbers, dates, times: always via
Intllocale-aware formatting (Arabic locale digits/date conventions), never manual formatting. Units shown in UI (ml, minutes, hours) are rendered from API values through ICU messages. - Translation workflow (who translates Arabic, review process): undefined — open question (§9).
5b. RTL Strategy (Arabic)
Full RTL is a spec requirement including mirrored progress bars and layout mirroring:
- Root direction:
dir="rtl"(andlang="ar") set on the document root when locale isar;dir="ltr"/lang="en"otherwise. Direction flows from the locale, not a separate toggle. - CSS logical properties throughout:
margin-inline-start,padding-inline-end,inset-inline-start,border-start-start-radius,text-align: start, etc. Physical left/right properties are banned in shared components (lintable via stylelint — tooling open). This makes most of the layout mirror for free. - Progress bars: built so fill direction follows the inline axis (logical properties / direction-aware flex), so the hydration progress and any linear meters fill right-to-left in Arabic per spec.
- Charts: the timeline and any plotted history must mirror — time axis runs right-to-left in RTL, tooltips/labels flip sides. Charting library choice is PENDING DESIGN + open decision; RTL-mirroring capability is a hard selection criterion for whatever is chosen.
- Node graphs / Human Node diagram: the geometric status diagram must also mirror in RTL (spec: mirrored "node graphs"). Which elements of a human-figure diagram are direction-sensitive vs anatomically fixed is PENDING DESIGN (§6, open question §9).
- Circular elements: whether the hydration ring's sweep direction mirrors in RTL is PENDING DESIGN (spec mandates mirroring for progress bars; circular sweep is not specified).
- Icons: direction-sensitive icons (arrows, chevrons, "next/back") flip in RTL; universal symbols do not. Icon set itself: PENDING DESIGN.
- Verification plan: every screen reviewed in both
en/LTR andar/RTL before sign-off; pseudo-locale or automated screenshot diffing in both directions is a proposed QA aid (tooling open).
6. The "Human Node" Geometric Status Diagram — PENDING DESIGN
A geometric status diagram (human/node figure) intended for the dashboard. Its visual form, geometry, states, interactivity, and exact data mapping are entirely PENDING DESIGN in the separate Figma/design track. What this plan commits to now, independent of visuals:
- Home:
packages/ui/human-node/, rendered as SVG (proposal — SVG suits stroke glow viadrop-shadowin dark mode and mirrors cleanly under RTL transforms). - Data in, nothing computed: it visualizes the same
['engines','summary']query the status cards use — it must not derive any status locally. - Theming: strokes/fills bound to semantic tokens so it glows in dark mode and renders flat in light mode automatically.
- Blocked until design lands: geometry, node-to-engine mapping, states/animations, RTL mirroring rules, accessibility description of the graphic.
7. Component Inventory Sketch
All items below are shared components (packages/ui) reused by Electron in Phase 6 unless noted. Visual specifics of every one of them are PENDING DESIGN; this inventory defines responsibilities and data contracts only.
| Component | Responsibility | Data source | Notes |
|---|---|---|---|
| Today Timeline | Chronological view of today's logged events (water, caffeine, food, medication, pomodoro sessions) and engine window boundaries as returned by the API | ['timeline', date] | Mirrors in RTL (time axis flips). No local event classification. |
| Engine Status Cards (×8) | One card per protocol engine showing current server state (e.g., caffeine block active/cleared, GERD window open/closed, cycle "needs 3 cycles" state) | ['engines','summary'] + per-engine queries | Active-timer cards use the neon-green glow token in dark mode. Countdown displays animate toward server deadlines only (§1). |
| Hydration Ring | Circular progress toward daily hydration as reported by the API; quick "log 250ml" action | ['engine','hydration',date] + log mutation | Server enforces 250ml units, 5000ml max, 30s cooldown — the ring renders results and server rejections; it enforces nothing. RTL sweep behavior PENDING DESIGN. |
| Human Node Diagram | Geometric at-a-glance status figure | ['engines','summary'] | Entirely PENDING DESIGN (§6). |
| Notification Center | List + unread state for backend smart alerts; mark-as-read | ['notifications'] | Alert styling uses #FF3333 glow token in dark mode. Delivery mechanism (poll vs push) depends on Phase 1 (§9). Browser Notification API usage goes through the web platform adapter so Electron can substitute its own. |
| Log Entry Controls | Buttons/forms to log water, caffeine, food (categorical pick — Safe/Trigger-bearing rendering comes from backend), medication, cycle events, contraceptive events | mutations in packages/api-client | Pure input collection; all validation verdicts come from the server and are displayed verbatim, including "Consult your doctor" deferrals on AI-adjacent surfaces. |
| Pomodoro Card | Shows kinetic pomodoro state; start/stop actions | ['engine','pomodoro','current'] | Movement verification comes from wearables via the backend — the web card only displays whether the break registered. |
| App Shell / Nav | Layout, navigation, locale + theme switchers | — | Lives in apps/web (not shared); Electron gets its own shell in Phase 6. Structure PENDING DESIGN. |
| Alert/Toast primitives | Transient feedback incl. server rejection reasons | — | packages/ui/feedback. |
8. Cross-Cutting Quality Notes
- Accessibility: color is never the only status signal (glow/color pairs with text/icon state); the Human Node diagram needs a text alternative (PENDING DESIGN). Contrast of
#00FF33on#1A1A1Aand light-mode accents on white should be verified during design finalization — flagged to the design track, not resolved here. - Performance: route-level code splitting via TanStack Router; heavy PENDING-DESIGN visuals (charts, Human Node) lazy-loaded.
- Testing (proposed): component tests for shared
packages/uiin both themes and both directions (LTR/RTL); contract tests forpackages/api-clientagainst the Phase 1 API schema once it exists; a "no hardcoded protocol constants" lint/audit as a CI gate. - Type safety: if Phase 1 publishes an OpenAPI (or equivalent) schema from the ToGO API,
packages/api-clienttypes should be generated from it rather than hand-written — depends on Phase 1 tooling (open question §9).
9. Open Questions
- Push vs poll: Does the Phase 1 ToGO backend expose a push channel (SSE/WebSocket) for engine state transitions and smart alerts, or does the web client rely on polling + refetch-on-focus?
- API schema artifact: Will Phase 1 publish a machine-readable API schema (e.g., OpenAPI) for generating the typed client? Final endpoint paths and response shapes for engine deadlines/states are owned there.
- Preference persistence: Are theme and locale preferences server-persisted (cross-device sync) or local-only per client?
- i18n library: FormatJS/react-intl vs i18next+ICU (both satisfy the ICU requirement) — and the Arabic translation/review workflow.
- Workspace tooling: monorepo manager choice (pnpm workspaces / npm workspaces / turborepo) for the
apps/+packages/split that Phase 6 Electron reuse depends on. - Charting library: must support RTL mirroring and CSS-variable theming — selection blocked until the design track defines the chart visuals (PENDING DESIGN).
- Light-mode alert color: spec defines dark-mode alert red (
#FF3333) but no light-mode alert color — needs a design decision (PENDING DESIGN). - Human Node diagram: all geometry, states, engine mapping, RTL behavior, and accessibility treatment (PENDING DESIGN).
- RTL behavior of circular progress: does the hydration ring's sweep mirror in Arabic? (Spec mandates mirrored progress bars; circular sweep unspecified — PENDING DESIGN.)
- Login/session UX: TOGO auth integration details (session model, token handling, sign-up vs invite) are owned by Phase 1 and undefined for the web client until that plan lands.