Living document. It is verified on every push by
npm run docs:check(see Keeping docs fresh). Last reviewed: 2026-09-13.
Wird is a daily Islamic habits tracker — Next.js 16 + React 19, 100% on-device.
No database, no accounts server, no analytics beacon. Everything lives in the
browser's localStorage (per profile), with export / QR / LAN / recovery-word
tools to move between devices.
- Stack: Next.js 16 (App Router, Turbopack) · React 19 · TypeScript 5.9 (strict) · ESLint 9 flat · Prettier · Vitest · Playwright
- Runtime deps:
next,react,react-dom,qrcode,jsqr— nothing else. - Node 22 (
.nvmrc). Package manager: npm, exact versions (.npmrc).
- Quick start
- Routes (pages)
- Features by route
- Data & storage
- Profiles, login & recovery
- Moving to another device
- Internationalization & themes
- Library data (Quran / Hadith / paths)
- Security model
- Quality gates (tests, lint, CI)
- Deployment
- Troubleshooting
- Keeping docs fresh
- Adaptive companion
- Analytics methodology
- Companion decisions
- Privacy model
- Testing guide
- Architecture
- Components
- Contributing
- Changelog
- Domain models
- Internal API
- Versioning & migrations
- Performance
- Error handling
- Localization
- Accessibility
- Privacy architecture
- Network access
- Architecture decision records
- Features index
- How to add a feature
- How to add a language
- How to add religious content
- Governance
- Reproducibility
- Dependency governance
- Release engineering
- Feature flags
- Technical debt register
- Contributor levels
- Maintainer handover
- Open-source readiness
- Safe code generation
- Offline architecture
- Companion project (design record)
- Adaptive intelligence
- Reliability architecture
- Terms of Use · Privacy Policy (also served at
/terms,/privacy) - Data inventory · Classification · Retention · Content rights
- Safety (youth + roadmaps) · Compliance matrix
- Master audit · Audit report (this run) · Audit baseline · machine inventory in
audit/(npm run audit:manifest) - Limitations · Deen journey · ADR-007
- SEO report
npm ci # exact lockfile (never npm install on a fresh clone)
npm run dev # http://localhost:3000 (Turbopack)
npm run build # production build (also prerenders /opengraph-image, /robots.txt, /sitemap.xml)
npm run start # serve the production build
npm run lint # eslint . --max-warnings 0 (flat config)
npm run typecheck # tsc --noEmit (strict + noUncheckedIndexedAccess)
npm run test # vitest run (unit)
npm run test:e2e # playwright test (spins up production server)
npm run format # prettier --write .
npm run docs:check # verify this file matches the code (keys + routes)
npm run boundaries:check # architectural boundary gates (lib↔components, schema, online-only, privacy↔CSP)
npm run metrics # maintainability report (oversized modules, fan-in, runtime cycles)
npm run sbom # regenerate sbom.wird.json (CycloneDX-lite)
npm run clean # wipe .next + tsbuildinfo (run after major upgrades)Conventional commits are enforced (feat:, fix:, style: …) via commitlint +
husky pre-commit (lint-staged) + commit-msg hooks.
| Route | File | Purpose |
|---|---|---|
/ |
app/page.tsx |
Today: hero, habits, rescue plan, goals, night review |
/calendar |
app/calendar/ |
30-day month grid, Friday plan |
/insights |
app/insights/ |
Real stats, balance radar, coach brief, Hijri year |
/review |
app/review/ |
End-of-day checklist (tri-state) + score + mood |
/deen |
app/deen/ |
Deen journey: levels, quests, deeds, reflections, streaks (see §3) |
/library |
app/library/ |
Tabs: Adhkar · Quran · Hadith · Paths · Dreams |
/account |
app/account/ |
Profile, backup, transfer, reminders, times, theme |
/recovery |
app/recovery/ |
Last-resort recovery: inspect, emergency export, restore, surgical reset |
/private-plans |
app/private-plans/ |
Optional private self-management plans (discreet, unlisted from nav) |
/terms |
app/terms/ |
Terms of Use, bilingual, versioned (static, precached) |
/privacy |
app/privacy/ |
Privacy Policy, bilingual, versioned (static, precached) |
Shared chrome (sidebar, bottom nav, header, zikr dock, login gate) lives in
app/components/shell.tsx; all state in app/components/wird-store.tsx
(WirdProvider + useWird()). Views that need props live in
app/components/views/.
Today (/) — hero ring (live %), day-mode pills, focus card, quick tools,
rescue plan (one-now, Quran timer session, daily dua), ramp card, intention
card, Friday card, favorites, don't-forget, tawbah, moment grid (next prayer
countdown, intention, tasbih counter), filterable habit cards, extras grid,
Quran callout, adhkar tags, dua card, goals + challenges + pledges, night
reflection (persisted textarea), tiered return screen after ≥3 absent days
(short/gentle/journey/deep-restart + optional tawbah path for ≥14 days),
adaptive companion card (one ranked guidance + verified verse/hadith, see
§14), kids quests, qada / fasting / breaker cards, Ramadan banner in Ramadan.
Review (/review) — auto-built checklist from today's real data plus six
heart-check items; tri-state (done/partial/missed); mood + gratitude; weighted
score saved immutably per day (wird-reviews-v1, merged into history);
one-line night context for strong/low/return days (see §14).
Insights (/insights) — period pills (1–365d buckets), weekly bars, 4
metrics, coach brief (at-risk → neglect → pace → lift + praise), 6-axis
balance radar (SVG), 30-day strip, Hijri year-in-review card, share-as-image,
plus the analytics layers (see §15): overview with personal baselines,
14/30-day trends with evidence, habit comparison + per-habit best/weakest
windows, weekday patterns (min samples), streak intelligence (avg run/break),
Quran + memorization recency, adhkar trends, goal/challenge/pledge stats,
reflection/gratitude/mood metadata (no diagnoses), return analytics
(continuations, rebuild speed, break shortening), smart insights with
why-lines, GitHub-style heatmap with faith disclaimer, month + year reviews,
milestones, and a custom date range. Nothing renders without evidence.
Library (/library) — Adhkar groups with per-item counters; Quran reader
(full Uthmani text offline, EN translation toggle, 11 tafsirs incl. offline
Jalalayn, 12 reciters with ayah-follow audio, search, bookmarks, memorized
marks); Hadith (curated selections with EN + full 12-book browser with lazy EN translations: chapters,
search, jump-to-number, pagination, read/fav/copy); learning paths (8 sciences
× 4 levels, custom books); dream board.
Account (/account) — profile hero, working settings accordion, new-day
reset, backup (plain/encrypted/import/wipe), cross-device transfer (QR/LAN/
recovery), demo mode, reminders + prayer times, appearance (theme/lang/
auto-lock), profile manager.
Private plans (/private-plans) — optional, discreet self-management
plans (abstinence / reduction / time-limit) with a neutral setback flow,
urge timer, triggers, replacements, milestones, and a journey timeline.
Local-only, excludable from backups, generic reminders; see
docs/features/private-recovery.md.
Deen journey (/deen) — five voluntary tracking levels (fard →
mustahabb → makruh awareness → character → refinement) with daily
prayers, quests, good deeds, speech/haram reflections (never scored),
streaks, combos, chest, achievements, and gamification toggles. Wird XP
is app activity only — never reward, rank, or faith; see
docs/features/deen.md and ADR-007.
All state persists in localStorage under wird-* keys. Daily keys reset
automatically ({day, …} envelope checked against today's dayId()).
Per-profile keys are prefixed p_<profileId>_ (see §5); the backup collector
understands both shapes.
Daily (auto-reset): wird-done-v2, wird-quran-pages-v2, wird-tasbeeh-v2,
wird-fast-v2, wird-forget-v2, wird-reflection-v2, wird-partial-v2,
wird-snoozed-v2, wird-salawat-v2, wird-adhkar-groups-v1, wird-lastseen-v1,
wird-notify-day-v1.
Persistent: wird-customs-v1, wird-duas-v1, wird-goals-v1 (entries may
carry additive created/done; older goals without them read as unknown),
wird-intention-v1, wird-daymode-v1, wird-qada-v1, wird-breaker-v1,
wird-challenges-v1, wird-ramp-v1, wird-friday-plan-v1,
wird-quran-bookmark-v1, wird-quran-mem-v1 (new marks carry @dayId
recency suffixes; bare legacy marks still match), wird-quran-font-v1,
wird-quran-en-v1, wird-reciter-v1, wird-tafsir-src-v1,
wird-hadith-fav-v1, wird-hadith-read-v1, wird-paths-v1,
wird-paths-custom-v1, wird-dreams-v1, wird-kids-v1, wird-pledges-v1,
wird-history-v1, wird-reviews-v1, wird-remind-v1,
wird-adhkar-log-v1 (per-day group counts, capped 180 days),
wird-recovery-plans-v1 (per-profile private self-management plans —
sensitive; excludable from backups, see docs/features/private-recovery.md),
wird-deen-v1 (per-profile deen journey: level, XP ledger, quests,
achievements, streaks, day records, customs, gamification settings —
sensitive; day records pruned >365d, see docs/features/deen.md),
wird-personalize-v1 (per-profile adaptation switches: master, habits,
mood, reminders — see docs/ADAPTIVE.md).
Device-global (never namespaced): wird-profiles-v1, wird-active-profile,
wird-theme-v1, wird-lang-v1, wird-reminders-v1, wird-mosque-v1,
wird-prayer-times-v1, wird-autolock-v1, wird-recovery-v1,
wird-pinlock, wird-pinlock-*, wird-unlocked (sessionStorage),
wird-quarantine-v1, wird-health-v1, wird-last-backup-v1,
wird-guide-log-v1, wird-privacy-names-v1, wird-analytics-optout-v1,
wird-vault-v1, wird-travel-v1 (hidden-from-extra-eyes profile IDs),
wird-export-log-v1 (what-left-the-device consent log, capped),
wird-private-plans-excluded-v1 (private-plans backup exclusion flag),
wird-safe-mode-v1 (device-global degraded boot flag),
wird-probe-v1 (transient storage-probe key, written + removed in one call, never persisted)
(diagnostics, see below).
Helpers: loadFromStorage / saveToStorage (app/lib/wird.ts, profile-aware),
useStoredState (app/lib/use-stored-state.ts, hydration-safe), nsKey
(app/lib/profiles.ts). State initializes with static fallbacks so SSR HTML
matches, then hydrates stored values on mount — never read storage during
render (see docs/adr decision in git history: hydration fix).
Integrity layer (app/lib/schema.ts, zero-loss contract):
- Every dataset has a versioned
Schema{ version, validate, normalize?, migrate?, fallback } inSCHEMAS. Validators never strip unknown fields; migrations must spread old data. - New writes are enveloped
{ __wird: { v, updatedAt }, d }; legacy bare values still read as v0 and are never rewritten in place. - Read path: parse → unwrap → future-version guard (preserve untouched,
serve fallback) → migrate → validate → normalize-salvage → quarantine.
Malformed records land in
wird-quarantine-v1(last 20, ~200KB cap) with their raw bytes; events append towird-health-v1(last 50). Pre-envelope v1 shapes (bare array/number/string under daily keys) migrate to{day:"", …}(undated legacy); daily loaders showday:""as current only when the data provably came from storage (readTrusted), so corruption/missing keys can never masquerade as legacy. - Write path: validate first; when a collection has salvageable items it
heals on write (persists the valid subset, quarantines rejects) instead
of refusing the whole dataset; quota failures return
{ ok:false, quota:true }and raise an in-memory notice (a full store cannot persist the notice itself). - Import (
app/lib/crypto.ts):previewRestoredry-runs any backup map;restoreBackupSafeclassifies EVERYTHING first (zero applicable entries → rejected before a single write, not even quarantine), then snapshots, applies valid, salvages partial, quarantines corrupt, and rolls the snapshot back on mid-restore failure.restoreBackupkeeps its(data) => numbersignature for existing callers. Exports are manifest-wrapped (buildBackupFile→{ v:2, app, format, appVersion, exportedAt, encrypted:false, count, datasets:{version,records}, data }); encrypted transfers wrap the same manifest INSIDE the ciphertext (wrapForEncryption/unwrapDecrypted, legacy raw maps still accepted); every success stampswird-last-backup-v1({at,kind}) for diagnostics.parseBackupFilestill accepts legacy v1 and raw key maps. - UI: account page has an import pre-flight confirm (corrupt/salvageable
counts), double-confirm wipe (quarantine + health survive a wipe), and a
data-health card (counts, last entries, diagnostics export, log clear).
The provider shows a quota banner (from drained notices) and a multi-tab
banner on
storageevents — reload is always explicit, never auto-merge. - Local diagnostics (
app/lib/diagnostics.ts, counts only — no personal content): profiles, datasets, stored/quarantined/future-version keys, last backup, last migration, service-worker and transfer readiness; emergency export (buildEmergencyExport) with an honest recovered/corrupt/skipped report for manual rescue. - Performance posture: saves stay per-key on change (no full-store serialization per interaction); validation is O(dataset) with tiny datasets; backup encryption, QR chunking, gzip, and emergency export run on demand from explicit buttons only; quarantine/health are capped lists.
Download filenames (not storage, also matched by the checker):
wird-backup-*, wird-backup-enc-*, wird-diagnostics-*,
wird-emergency-*.
- Profiles: name + avatar + optional 4–8 digit PIN (
app/lib/profiles.ts); ids arecrypto.randomUUID()(timestamp ids could collide across devices on import). Registry, verifiers, and demo seeds all flow through the schema layer; deleting a profile also purges its quarantine raws (health entries carry no payload and stay for forensics). - PINs stored as SHA-256 (
wird-pin:<pin>domain); 5 wrong tries → 30s lockout. - Auto-lock timer re-locks after inactivity (configurable, off/5/15/30/60 min).
- Recovery: 12-word BIP39 phrases (
app/lib/recovery.ts, wordlist bundled atpublic/data/bip39-en.txt) reset forgotten PINs; verifiers inwird-recovery-v1. Tested against the official zero-entropy vector. Phrases are shown once on demand, never logged (zeroconsole.*inapp/), never in URLs, never persisted (only the verifier, scoped per profile id — a phrase for A cannot silently reset B), cleared from state after use, and resetting still requires the new PIN (phrase ≠ auth shortcut). - Switching profile reloads the app so no cross-profile state leaks; every async import captures the active profile id first and aborts if it changed mid-decrypt, so imports can never commit into the wrong profile.
- Anonymity: optional name-hiding on lock/login screens
(
wird-privacy-names-v1, Account → privacy); PIN fields useautocomplete="new-password"+ no spellcheck against autofill and shoulder-surfing dictionaries. - PIN hardening: trivial PINs rejected at set-time; destructive backup actions (plain export, wipe) re-verify the profile PIN inline when one is set; recovery verifiers are per-profile salted (v1) with legacy unsalted (v0) hashes still accepted.
- Duress PIN (optional, explained in UI before setting): opens a shared blank decoy profile, counts nothing against lockout, leaves real data locked. Must differ from the real PIN.
- Reflection vault (optional, strongly discouraged in UI): AES-GCM
reflection encryption with session-only keys; forgetting the passphrase
loses the data permanently by design. See
docs/PRIVACY.md. - Catastrophic failure →
/recovery(independent of the main store): storage-health inspection, emergency export, backup restore, per-dataset surgical reset, full erase only by typing DELETE.
Account → transfer card (app/components/transfer.tsx):
- Encrypted file — AES-GCM export/import (also the plain-JSON option).
Fresh 128-bit salt + 96-bit IV per backup, PBKDF2-SHA256 120k, GCM auth
rejects tampering; passwords live only in memory, failures are generic
(
bk.bad/tr.badPin— no crypto internals leak). - QR snapshot — PIN-encrypted + gzipped manifest envelope as animated
WIRD1:i/n:codes; scanner assembles in any order (app/lib/transfer.ts). Hardened: structural chunk check (isSaneChunk, ≤600 chunks ≈ 1MB — bigger must use files), per-session lock (foreign-nchunks never mix), duplicates collapse, contaminated sets refuse assembly, staged progress (verify → decrypt → validate → import), nothing commits before preview + validated restore. - Local Wi-Fi pair — WebRTC DataChannel with manual SDP exchange,
host-candidates only: no STUN, no server, no internet (
app/lib/lan.ts). Hardened: malformed SDP → pair-code error (not "wrong password"), 20s connect timeout, bounded LAN payload (≤8 parts, ≤32MB), empty payload explained, sessions closed on unmount, validated restore only. - Recovery phrase — resets PINs; backup files restore content.
- 1295-key AR/EN dictionary (
app/lib/strings.ts, parity enforced bydocs:check);useT()hook +tr(); religious/user content stays Arabic. document.dirflips rtl/ltr;[dir="ltr"]CSS mirrors layout.- Themes light/dark/oled via
data-theme+tokens.css; OS preference respected on first run; persisted per device. - Hijri dates via
Intlislamic-umalqura; prayer countdowns manual times.
- Quran Uthmani (6,236 ayahs), Clear-Quran English, Jalalayn tafsir, BIP39
wordlist, Nawawi 40: bundled in
public/data/, lazy-fetched, SW-cached. - 12 full hadith books + 11 online tafsirs + 12 reciter MP3s: fetched on demand from CDN/API (CSP-allowlisted), cached by the service worker afterwards.
- Curated hadith selections + learning paths live in
app/lib/data/with English translations and grades.
npm auditclean (CI-gated); 5 runtime deps only.- Headers (
next.config.ts):X-Content-Type-Options,X-Frame-Options: DENY, strictReferrer-Policy,Cross-Origin-Opener-Policy, least-privilegePermissions-Policy(camera/mic/geolocation/payment all denied — voice logging was removed rather than leak audio to cloud STT), production-only CSP (scriptunsafe-inlineis a documented Next.js requirement; fonts self-hosted so no font CDN needed). - AES-GCM backups (PBKDF2 120k, random salt/IV per backup, GCM tamper
detection, generic failure messages, passwords never stored/logged),
PINs hashed, recovery verifiers hashed, no
dangerouslySetInnerHTML(tafsir HTML stripped via detached node). - Privacy page principle: nothing leaves the device except user-initiated transfers/shares; one-tap wipe in Account.
npm run typecheck(strict +noUncheckedIndexedAccess),npm run lint(--max-warnings 0),npm run format:check,npm run docs:check(7 checks),npm run comments:check,npm run css:check,npm run boundaries:check,npm run metrics(informational report).- Vitest (282 tests, 34 files): history/coach/recovery/transfer/demo logic plus the reliability
suites —
schema(envelopes, quarantine, salvage, future-versions, quota, caps, migration idempotence),crypto-restore(manifest, dry-run, two-phase zero-write rejection, snapshot rollback, salvage),crypto-security(round-trip, wrong password, GCM tamper, malformed payloads, salt/IV freshness, legacy compat),daily(midnight rollover, multi-day absence, legacy bare shapes, monotonicrecordDay),isolation(namespacing, adoption, id uniqueness, quarantine purge),fuzz(seeded: validators/read/write never throw, valid state round-trips), transfer edges (duplicates, order, contamination, bounds),companion(26 user states, ranking, fatigue/once-ever, rotation, core, log caps),content(verse refs exist in bundle, legacy refs parse, hadith resolve with grade+ref, per-theme coverage),guidance-safety(auto-collected AR+EN keys: presence, no revelation markers, no shame/ruling/heart/medical/causation claims, distinct source labels),analytics(20-test BM suite over a synthetic 4-month dataset: periods, trends, streaks, returns + rebuild days, restart-size evidence, habit windows, weekday gating, honest time-of-day null, Quran/mem/adhkar, goals/challenges/pledges, gratitude + mood metadata, heatmap relativity, month deltas, what-changed, friction, insight gating, milestones, data quality, DST safety, volatility, analytics→companion pipeline),prayer(day-arc fractions incl. overnight wrap, next-prayer agreement),vault(setup/unlock/lock/disable, generic failures, no plaintext residue), weak-PIN + duress round-trips (inisolation), salted + legacy verifiers (inrecovery),private-plans(run math, history-preserving resets, neutral copy, generic reminders, schema round-trip). - Playwright (54 tests, prod server, serial
--workers=1pinned innpm run test:e2e): toggles persist, routes render, theme/lang persist, tilt vars, transfer QR + recovery flows,/recoveryhealth + emergency export, corruption survival + quarantine, A/B profile isolation across switches and reloads, companion return journeys (fresh start, 10-day gentle return with working action, 95-day deep restart with tawbah path, no-shame scan), insights layers with seeded data + honest empty state, private-plans lifecycle (create → check in → setback with history preserved), quick exit, URL/title leak scan, offline suite (external blocked + full browser offline: core flows, bundled search, plans, calm CDN failures, true-offline restart, library regressions (no-duplicate-button, Ahmed/Darimi chips, Tazkirul source, localized fav label), fatigue notice + settings persistence, safe-mode parks + core-works + exit; hydration-error listener fails the run on mismatch. - CI (
.github/workflows/ci.yml):npm ci→ typecheck → lint → format:check → unit → Playwright chromium → test:e2e → build → docs:check → comments:check → css:check → boundaries:check → metrics → audit. - Supply chain:
npm auditclean; SBOM (sbom.wird.json) + third-party licenses (THIRD_PARTY_NOTICES.md), all regenerable vianpm run sbom/npm run audit. Seedocs/DEPENDENCIES.md.
Vercel recommended (zero config). Set NEXT_PUBLIC_SITE_URL to the real
domain so OG/canonical URLs are exact. Any static-capable host works with
next start; service worker + manifest ship from public/.
- Old UI after update → in-app "تحديث متاح" banner (SW
SKIP_WAITING); else hard-refresh, or DevTools → Application → unregister SW on localhost. - Dev server weirdness →
npm run clean, restartnpm run dev(only one dev server per folder — Next enforces the lock). - Build fails on
/_not-foundafter upgrades → stale.nextcache;npm run cleanfirst. - E2E fails locally → e2e runs against a production server by design
(
next devHMR sockets break in sandboxes); the server listens on 127.0.0.1:3119 — ensure that port is free (npm run devitself uses :3000). - App looks broken / data suspect → open
/recovery: storage health, per-dataset statuses, emergency export, backup restore, surgical reset. Corrupt records are quarantined (wird-quarantine-v1), never silently emptied — export diagnostics from Account → data-health card before resetting anything. - "Couldn't save: storage is full" → export a backup first, then delete old data (demo/history); the banner names the failing dataset.
- Data changed in another tab → banner offers an explicit reload; the app never auto-merges across tabs.
npm run docs:check (scripts/check-docs.mjs, runs in CI) asserts:
- Every
wird-*storage key literal inapp/is named in §4 above. - Every route directory under
app/is named in §2 above. - AR/EN dictionary key parity.
- Every internal markdown link resolves (anchor-aware).
- Every file under
docs/is indexed indocs/README.md(docs/features/README.mdcoversdocs/features/*). - Doc quality: no placeholder or task-marker text, every npm run
reference names a script that exists in
package.json, everyNode Nmention matches.nvmrc(22). - Release version stamp:
WIRD_APP_VERSION(app/lib/crypto.ts) equals thepackage.jsonversion.
npm run comments:check (scripts/check-comments.mjs) asserts every
non-test source file opens with a purposeful 2+ line header comment.
When adding a key, route, or feature: document it here in the same PR — CI fails otherwise.
Local, deterministic, offline guidance layer (app/lib/companion.ts,
app/lib/content.ts, app/components/companion.tsx). No AI service, no network,
no analytics — pure functions over existing history/state, memoized in
components.
- Engine:
assessUser()derives 26 internal states (first use, new user, first week, active, consistent, momentum, improving, slipping, struggling, quiet, 4 absence depths, returning, rebuilding, strong return, repeated restarts, overload, challenge milestone, strong/low/ recovery day, Friday, Ramadan, post-Ramadan) with personal-baseline metrics (never global scores) and high/medium/low confidence. Low confidence yields only gentle generic states — never specific claims. - Ranking: return family → today shapes → milestones → trends →
calendar overlays (Friday/Ramadan merge as one line into the card when
another state leads). One card maximum on Today; milestones fire once
ever per identity; repeats are suppressed for 3 days
(
wird-guide-log-v1, capped at 30) — the UI shows nothing rather than nagging. - Content: verse references only resolve at runtime against the bundled Uthmani + Clear-Quran datasets (missing → renders nothing); hadith resolve against the curated library with book + grade + ref. Quran / Hadith / Wird-suggestion blocks are visually and semantically separated; guidance copy is scanned by tests for revelation markers, rulings, shame, and heart-claims.
- Surfaces: adaptive card on Today (one action: rescue/review/core/ intention/tawbah/quran/friday), tiered return screen (3/7/14/30/90-day copy + optional tawbah fresh-start path for ≥14 days, Friday merge), rescue-plan context line, night-review line. Destructive-adjacent actions (new-day reset, demo clear) confirm explicitly; reflections/moods never leave the device and never enter share images (stats only).
- Analytics feed: return cards cite the pre-break steadiest habit and
small-vs-large restart evidence (
restartSizeEvidence); rescue lines cite evening-vs-morning friction; challenge milestones flow fromchallengeStats. See §15 /docs/ANALYTICS.md. - Safety rules: no shame, no rulings, no iman/sincerity/acceptance claims, no dream interpretation, no medical claims, kids get the same gentle copy, prayer times are referenced (never "you missed X" unless tracked).