Skip to main content

Architecture — a C4 model

This is a C4 model of Rewhaven: a zoom-in from the whole system (who uses it, what it talks to) down to the load-bearing code. It is the living source of truth for the architecture — when a load-bearing decision changes, update these pages. They are hand-authored (not generated), because the value is the curation and the why.

:::tip For AI agents & humans — start here These pages plus graphify are the canonical reference for this codebase. graphify answers "where is the code / how are things related" (graphify query "<q>"); these pages answer "how it fits together, why, and what's external or planned". Dashed nodes are planned / gaps — never assume a dashed thing is built. When a load-bearing decision changes, update these pages. :::

Read top-down:

Cross-cutting & platform:

Diagram legend

Every C4 diagram uses the same colour vocabulary (the design_system calm-dusk palette). Dashed nodes are planned / stubbed, not yet built.

The one data path

Every UI-to-storage interaction flows through exactly one chain. Domain rules live in the SDK Service; repositories are thin presentation delegates.

  • Widget — dumb leaf UI; no logic, no I/O.
  • Bloc / Cubit — state + side effects (created in a page's wrappedRoute()).
  • Repository — a thin delegate adapting the SDK facade to blocs; no domain rules.
  • Client (facade) — the client_sdk public surface; the only data-layer type presentation knows.
  • Service — where the domain rules live.
  • Adapter — pure I/O against the local store (Drift) or cloud (Supabase).

Load-bearing rules

  • Invariants enforced in the service AND the schema — append-only ledger, zero-floor balances (a debit throws and a SQL trigger blocks it), expectation-pays-zero, household-scoped RLS.
  • SDK construction is config-drivencreateClient({required ClientConfig config}); ClientConfig(api: ApiConfig(...)) switches local→cloud. The production local store is a write-through cache over Drift.
  • Presentation never imports drift / supabase — only the client_sdk facade.
  • Brand lives only in app/lib/inside/i18n + store metadata — never in names.

Inside vs Outside

The app layer splits singletons built before the widget tree (outside — client providers, effect providers, repositories) from things created inside it (blocs, effects, widgets/pages). Only repositories and effect providers cross from inside to outside; nothing in the tree touches a client provider directly.

Evolution from the POC

These diagrams encode what the POC (Chore_app) taught us. The biggest shifts:

  • Mutable balances → an append-only ledger. The POC mutated wallet balances in place; the rebuild projects balances from immutable LedgerEntry rows with a zero-floor invariant. See POC domain gap analysis.
  • Monolithic client → facade + services + adapters. The POC's single client grew unbounded; the SDK now splits a thin facade over domain services over swappable adapters. See the rebuild spec.
  • Brand baked into class/file names → i18n + store metadata only, so the engine is brand-agnostic.
  • Hand-maintained .g.dart → CI codegen, with model-agnostic design_system tokens. See design system decisions.
  • Testing as an afterthought → a two-layer pyramid (flow + integration). See testing strategy.
  • Engagement track (Wave-1/2) → a new architectural pattern: pure projections. Celebration, achievement badges, weekly streaks, and the child timeline are derived read-only views over existing facade reads — zero new storage. This pattern (cubit/bloc as projection, not as mutation owner) is now the canonical model for motivational features.

Current strategic frame: depth in the ND-child vertical, not wider — per docs/market-analysis-2026-07-expanded-positioning.md. Future authors: the product direction is a focused neurodiversity-affirming tool; resist drift toward super-app or "cover every household need" scope.