A free, kid-facing financial-literacy demo for wealthbot.io. Top-of-funnel brand content. Strictly educational: no real money, no RIAs, no personalized advice.
A working standalone micro-app:
index.html+app.mjs+styles.css— the front-end.app.mjsis an ES module Alpine component (registered viaAlpine.data()onalpine:init, so the CSP needs nounsafe-inlinefor scripts). State-driven World hub (props pop in as lessons complete), progress dots, star counter. Progress persists inlocalStorage; lessons lock until the previous one is done; completion is idempotent.lessons/— one self-contained ES module per lesson, pluslessons/index.mjs(the ordered registry). The core derives the lesson list, world props, progress dots, and reward titles from the registry. See Adding a lesson below.lib/lesson-kit.mjs— shared client-side lesson primitives (WEBO_SVG,weboHtml,propArt,speech,escapeHtml,mergeProgress,prefersReducedMotion) - pure + DOM-free so they unit-test.assets/— drop-in art. The app ships placeholder SVG; settingWEBO_ART(character) or a lesson'sprop.img(world prop) swaps in real art with no logic changes. Seeassets/README.mdfor the asset contract.api/ask.js— the Ask Webo proxy as a Vercel Node serverless function (POST /api/ask). Holds the Anthropic API key server-side (never shipped to the browser), applies starter safety guardrails (input + output moderation, per-IP rate limiting), and returns short, kid-friendly replies. Rate limiting uses Upstash/Vercel KV when configured, else best-effort per-instance.api/progress.js— anonymous cross-device progress codes (POSTto save a random code,GET ?code=to restore). COPPA-safe by design: the server rebuilds a clean, PII-free record from a strict whitelist (lesson ids + completed flags only), size-caps and TTLs it, and rate-limits per IP. No account, no login, no email.lib/kv.js— shared Vercel KV / Upstash helpers + fail-open rate limiting, used by bothapi/ask.jsandapi/progress.js.prototype/webo-money-world.html— the original single-file visual prototype (kept for reference).
Progress is local-first: it lives in localStorage per device and degrades
gracefully (private mode / quota / corrupt data never crash the app — they just fall
back to in-session progress). To move stars to another device, a child taps Save
and gets a short anonymous code; typing that code on another device brings the
stars over. The code maps to a PII-free record in KV (TTL ~90 days) and never
collects a name, email, or account. Account-based sync stays out of scope until the
COPPA parental-consent flow lands (issue #1). Restoring merges (it never
downgrades a star already earned on the current device).
The lesson platform (#29) makes a new lesson a single file:
- Create
lessons/<id>.mjsthat default-exports a lesson object:export default { id: 'giving', no: 'LESSON 7', name: 'Giving and Sharing', sub: '...', icon: '\u{1F381}', rewardTitle: 'You are a giver!', prop: { cls: 'prop-gift', html: `<svg>...</svg>` }, // or pos: { left: '30%', bottom: '120px' } run(ctx) { // ctx: { renderStep, speech, shuffle, finish(text), reduceMotion, ovBody, ovActions } ctx.renderStep(`<div class="lesson-intro">${ctx.speech('...')}</div> ...`, `<button class="btn" id="go">...</button>`); // wire interactions on ctx.ovBody / ctx.ovActions; call ctx.finish('reward text') to complete. }, };
- Import it in
lessons/index.mjsand add it to theLESSONSarray.
That is it - the lessons list, world prop, progress dot, and reward title all flow from
the registry. A prop is placed by a tuned CSS class (prop.cls in styles.css) or, for a
zero-CSS drop-in, an inline position (prop.pos). Add a test case to
test/registry.test.mjs if the lesson introduces new invariants.
See HANDOFF.md for the full build brief and the definition of done.
Full stack (lessons + Ask Webo) uses the Vercel CLI so the /api/ask function runs:
npm i -g vercel # once
cp .env.example .env # then put your real ANTHROPIC_API_KEY in .env
./run.sh # -> vercel dev on http://localhost:3000Front-end only (no key needed) works with any static server:
npx serveThe three lessons, world, and persistence all work with no key. Setting
ANTHROPIC_API_KEY lights up the Ask Webo chat. Without a key (or running
front-end-only), Ask Webo replies with a friendly "getting ready" message and the
rest of the app stays fully usable.
Pure-function unit tests (no dependencies, Node's built-in runner):
npm test # node --testThey lock the highest-value invariants from the security audit (issue #24): the
COPPA no-PII guarantee in sanitizeProgress, input normalization (cleanCode,
cleanClientId), the moderation denylist, the spoof-resistant clientIp, and the
KV layer (atomic incr-with-TTL, 3-state kvSetNx, fail-open / fail-to-low-cap).
The repo is Vercel-ready (vercel.json): the repo root is the static site, api/ask.js
is a serverless function, prototype/ and local files are excluded via .vercelignore.
vercel # link the project (first run) + deploy a preview
vercel --prod # promote to productionThen set environment variables in the Vercel dashboard (Project -> Settings -> Environment Variables):
ANTHROPIC_API_KEY(required)WEBO_MODEL(optional)- For durable rate limiting across instances, add a Vercel KV / Upstash Redis store
and its
UPSTASH_REDIS_REST_URL+UPSTASH_REDIS_REST_TOKEN(orKV_REST_API_URLKV_REST_API_TOKEN). Without it, rate limiting is best-effort per-instance.
| Item | Status |
|---|---|
| State persists across reload; locking + idempotent completion | Done |
| All three lesson flows fully playable | Done |
| World props, progress dots, star count driven by state | Done |
| Ask Webo via a server-side endpoint; no key in the client | Done |
| Moderation on Ask Webo input and output; rate limiting | Starter screen done; needs a real moderation model before launch |
| No PII collected or logged; COPPA posture confirmed | No PII logged; COPPA sign-off with counsel is a launch gate |
| Mobile-first layout | Inherited from the prototype (480px column); needs device QA |
| Adding a 4th lesson = array entry + flow fn + prop | Confirmed (see the comment on the lessons array) |
| Copy contains no em dashes; nothing implies real money/advice | Done |
A real moderation model (the regex screen is a responsible starter, not enough for under-13s), COPPA counsel sign-off before any public launch, a durable KV-backed rate limiter (wired but optional), accounts/login, analytics. These are the next steps.
Deploy target is Vercel (see above): static front-end on the edge + the Node
/api/ask function. The Anthropic key lives only in Vercel env vars.
main is protected: changes land via pull request, and Vercel deploys automatically.
- Branch off
main(git checkout -b feat/my-change). - Open a PR. Vercel builds a preview deployment and posts its URL + a status check on the PR.
- Merge to
main. Vercel deploys to production (https://webo-money-world.vercel.app).
Force pushes and branch deletion on main are disabled, and the Vercel preview check must pass before a PR can merge.