Skip to content

Repository files navigation

Webo's Money World

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.

What's here (MVP)

A working standalone micro-app:

  • index.html + app.mjs + styles.css — the front-end. app.mjs is an ES module Alpine component (registered via Alpine.data() on alpine:init, so the CSP needs no unsafe-inline for scripts). State-driven World hub (props pop in as lessons complete), progress dots, star counter. Progress persists in localStorage; lessons lock until the previous one is done; completion is idempotent.
  • lessons/ — one self-contained ES module per lesson, plus lessons/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; setting WEBO_ART (character) or a lesson's prop.img (world prop) swaps in real art with no logic changes. See assets/README.md for 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 (POST to 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 both api/ask.js and api/progress.js.
  • prototype/webo-money-world.html — the original single-file visual prototype (kept for reference).

Saving progress (issue #19)

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).

Adding a lesson

The lesson platform (#29) makes a new lesson a single file:

  1. Create lessons/<id>.mjs that 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.
      },
    };
  2. Import it in lessons/index.mjs and add it to the LESSONS array.

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.

Running it locally

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:3000

Front-end only (no key needed) works with any static server:

npx serve

The 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.

Tests

Pure-function unit tests (no dependencies, Node's built-in runner):

npm test          # node --test

They 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).

Deploying to Vercel

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 production

Then 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 (or KV_REST_API_URL
    • KV_REST_API_TOKEN). Without it, rate limiting is best-effort per-instance.

Status against the definition of done (HANDOFF.md §6)

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

Not in this pass (deliberately)

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.

Contributing / deploy workflow

main is protected: changes land via pull request, and Vercel deploys automatically.

  1. Branch off main (git checkout -b feat/my-change).
  2. Open a PR. Vercel builds a preview deployment and posts its URL + a status check on the PR.
  3. 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.

About

Webo's Money World — a free, kid-facing financial-literacy demo for wealthbot.io (educational only; no real money or advice).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages