A calm, adaptive early-math planner for the parent of a toddler β the child never touches the screen. Vanilla JavaScript, zero runtime dependencies, offline-first. One small, local-first app that plans, guides and records five-minute off-screen math moments at home.
βΆ Live demo (synthetic data) Β· Empty app Β· Latest release Β· User guide Β· Case study Β· Tech docs Β· Leia em portuguΓͺs
Interactive screens: the live synthetic demo is the always-current screenshot β open it on a phone or desktop.
Most "educational apps" solve early math by putting a screen in front of the child. For a 2.5-year-old that is the opposite of what the research and the pedagogy (Kate Snow's Preschool Math at Home) ask for: short, playful, hands-on moments with real objects β snap cubes, toy dinosaurs, dice, paper and pen.
The hard part isn't the math. It's that a busy parent, in the moment, doesn't know what to do right now, how to run it, or whether any of it is adding up. Math Trail is the tool that answers those three questions β for the adult.
The parent is the only user. The child plays off-screen with real materials.
- Plan β three optional daily windows (morning / afternoon / bedtime). Pick an activity or tap β¦ for a suggestion; the app shows a setup diagram, a parent script and why it was suggested.
- Do it β start a timed session, run the activity with the child, then end it.
- Log β three quick choices (how it went, the child's mood, how the challenge felt), plus an optional note. A finished session is held safely until you save it.
- Reflect β a history thread by day and a Progress tab: a skill trail, milestones being built, mood over time β as plain counts, never grades.
This is the central product decision, not an omission. Screen time is the thing this app replaces. Everything in the UI addresses the adult: scripts, non-evaluative logging language, mood tracked as an observation rather than a score. The child's experience is cubes and dinosaurs on a kitchen table.
- Zero dependencies, no framework β runtime and build. Even the linter is dependency-free (ADR-0003). The app deploys as static files and is meant to outlive framework churn.
- Deterministic, explainable engine β pure rules with an injected clock and RNG;
replayState(logs)is clock-free and temporal rules are evaluated at read time (ADR-0002). Honestly documented as a rule-based system, not machine learning (docs/model-card.md). - Data safety as a feature β schema-versioned backups, allowlist-validated imports with automatic snapshot + rollback, and a "verbatim-copy-or-nothing" migration policy β born from a real near-miss (case study).
- Child privacy first β no accounts, no server, no analytics, no third-party calls; data lives only in the device's localStorage. The public repo ships only synthetic data.
- Offline-first PWA β a versioned service-worker shell; installs to the home screen and runs with the network off.
- Full PT-BR / EN β interface and the entire 28-activity catalog.
- 153 automated tests, 0 skipped, run behind a gated CI pipeline.
A monolithic, modular vanilla-JS app. No bundler; ES modules loaded directly, with a build step that also emits a single-file bundle.
index.html βββ¬ββ js/app.mjs UI layer: rendering, plan, session flow, i18n wiring
βββ js/engine.mjs pure adaptive rules (level, mastery, story mode, cooldownβ¦)
βββ js/activities.mjs catalog: engine fields + EN display (single source of ids)
βββ js/activities-pt.mjs PT-BR display overlay (same ids)
βββ js/storage.mjs persistence, schema version, validation, snapshot + rollback
βββ js/session.mjs pure pending-session + demo-migration decisions
βββ js/time.mjs local-calendar time (localDateKey), timezone-correct
βββ js/i18n.mjs interface strings (PT-BR / EN)
βββ js/demo.mjs synthetic, seeded demo-data generator
sw.js Β· manifest.webmanifest PWA shell + install
Separation of concerns is strict: the engine is pure and clock-free, storage owns all persistence contracts, and app.mjs only wires them to the DOM. Domain state is always a pure function of the log, so editing or deleting a past session replays the whole history.
The interesting part lives in js/engine.mjs β pure functions, fully tested:
| Rule | Behavior |
|---|---|
| Level auto-adjust | Two consecutive "too easy" sessions β level up (1β3); one "too hard" β level down. |
| Mastery | "Too easy" twice at level 3 β activity marked well-explored, weight reduced in suggestions. |
| Story mode | 2 resisted sessions within the last 3 β scripts switch to adventure framing for 3 sessions. |
| Cooldown | "Too hard" at the floor level on composition activities β that skill rests for 48h. |
| Unlock gate | Two-Dice Count-On only enters the pool after Dice Flash is well-explored (ADR-0001). |
| Reward observation | If treats correlate with markedly more resistance than intrinsic play (min. sample enforced), the app gently suggests connection-as-reward. |
| Deterministic replay | State is recomputed from the log; a past edit can never leave state and history out of sync. |
| Stable daily plan | Suggestions are seeded by the local date, so the plan doesn't reshuffle on reload. |
It is not machine learning: no training, no weights learned from data, no inference. Every suggestion is traceable to a rule and shown to the parent. The ML roadmap below is deliberately future work.
- No accounts, no backend, no analytics, no third-party requests β verified: the deployed app makes zero external requests.
- All data is local (localStorage); nothing leaves the device unless the parent exports a backup file themselves.
- The public repository contains only synthetic data. An automated privacy guard fails CI if personal identifiers appear in any tracked file or in the built
dist/artifact. - Git history was scrubbed of earlier sensitive data (ADR-0004).
- See privacy.md, threat-model.md, data-inventory.md.
- No stored-content XSS β the history renderer builds DOM nodes with
textContentand real listeners; no log field ever reachesinnerHTMLor an attribute. Imports are validated and normalized to an allowlist (extra fields and__proto__payloads dropped). Threat model T3 mitigated with tests. - No silent data loss β imports snapshot first and roll back on any failure; migration is verbatim-copy-or-nothing.
- Timezone-correct β "today" and day-grouping follow the device calendar, with America/Sao_Paulo boundary tests.
npm run lint # zero-dependency lint + format gate
npm test # 153 tests: engine, timezone, storage/migration, privacy guard,
# catalog localization, XSS, pending session, demo migration, build
npm run build # deployable dist/ + single-file bundle in dist/standalone/CI runs lint β tests β build β artifact checks (PWA files, no personal data in dist/) β deploy. GitHub Pages publishes only the built dist/ artifact, never the repo root. main is protected: PRs required, the quality status check must pass (strict), no force-push. Deploys are gated on main.
A cache-first service worker precaches the app shell; the versioned cache name is bumped on every shell change so returning users get updates without clearing anything. Data already lives in localStorage, so the app is fully usable with the network off, and it installs to the home screen.
This project was built through AI-assisted engineering: models were used in separate roles β implementation, product review and independent audit β while requirements, acceptance criteria, product decisions and merge authorization stayed under human governance. One instance implemented; another audited; disagreements were resolved by checking the code, not by trusting a report; findings were not accepted without tests and evidence; CI and branch protection acted as gates; and no AI had autonomous authority to merge. The full workflow, including false positives that were withdrawn and fixes that were only accepted once a test proved them, is in docs/ai-assisted-engineering.md.
git clone https://github.com/samuel3ssilva/math-trail.git
cd math-trail
npm run serve # http://localhost:8123 (or just open index.html β there is nothing to install)Append ?demo=1 for three weeks of synthetic sample data.
index.html, styles.css, sw.js, manifest.webmanifest the app
js/ the modules (see Architecture)
tests/ 153 tests across 16 files; fixtures/ are synthetic-only
docs/ architecture, model-card, privacy, threat-model, ADRs, case study
docs/assets/ architecture diagram (SVG) used in this README
scripts/ zero-dependency lint + the seeded demo-data generator
demo/generated/ the committed reference demo dataset
- This is not a diagnostic or assessment tool, and makes no claim about a child's learning.
- The dataset a single family produces is small; recommendations and the catalog are intentionally simple.
- The engine is rule-based, not ML β no causal conclusions are drawn.
- Professional pedagogical validation is future work.
- Some detail is only verified by hand; see docs/manual-verification.md.
A deliberately conservative, evaluation-first path, kept separate from the shipped rule engine:
baseline_rule_v1 β synthetic dataset β a lightweight challenger model β champion/challenger evaluation β model card β an explicit decision to integrate or reject the model.
MIT β built by Samuel dos Santos Silva with AI as a pair-engineer and human judgment as the safety net.