Commit Graph
2 Commits
Author SHA1 Message Date
mergetest ad443e853a fix(donate): correct progress from $137.50 to actual $10/$200 raised
The in-app goal bar and its bundled snapshot showed $137.50 / $200 (23
sponsors) — fabricated numbers. Updated both the runtime JSON and the
TypeScript fallback to reflect the real amount: $10 / $200, 1 sponsor.

Also updates the README badge color from red to yellow (in-progress).
2026-06-17 12:23:03 +05:30
62637cad95 feat(donate): "Fund Claude Max" goal bar + kawaii postcard + milestones (#007) (#494)
Problem
  OmniVoice's donate surface was a static link page. There was no sense of
  shared progress toward a concrete funding goal, and no gentle, success-only
  ask after a user got value — only an always-on footer heart.

Design
  Phase 1 — Goal bar + data (Option B):
    - frontend/public/donation_progress.json (committed snapshot) + a bundled
      offline fallback in api/donation.ts. loadDonationProgress() best-effort
      fetches a fresher copy and gracefully falls back to the bundle on any
      failure (offline / non-2xx / bad JSON). Never throws.
    - <GoalBar> (page + `mini` variant), --goal-pct-driven fill, Pip mascot
      perched on the fill, ONE shimmer pass, reduced-motion guard on every
      animation. Added to SupportPage above the payment cards with
      "Join {n} supporters" social proof + suggested amounts ($3/$5/$10/Custom,
      middle flagged "most common", NONE pre-selected).
  Phase 2 — Pip + postcard + state machine:
    - Pip.jsx (currentColor->accent, pipBob/pipWave idle, reduced-motion off).
    - donationSlice.ts composed into the store: added to partialize (all EXCEPT
      shownThisSession), version 5->6 with a pass-through migrate branch.
      shouldShow rules: first-3 grace, <=1/session, escalating 7d/14d/30d/75d
      cooldowns, optedOut terminal, success-only.
    - Postcard.jsx rendered via react-hot-toast as a NON-BLOCKING custom toast
      (no backdrop, no focus steal, ~12s auto-dismiss, pause on hover) with the
      perforation / dot-grain / stampThunk / postcardIn / .is-leaving art,
      a mini GoalBar, and Chip in / Maybe later / quiet Don't ask again /
      free Star on GitHub actions.
    - One shared evaluateDonationPrompt() called right after each SUCCESS
      (dub-complete, clone-save resolve, longform export) — never on the
      error / in-progress / setup / first-run paths.
  Phase 3 — Milestones + pill:
    - Milestone eval (1st clone / 10th dub / 30-day sustained, each once-ever,
      same cooldowns + opt-out) inside the shared evaluator.
    - Quiet nav-rail .donate-pill (🩷 Support) that warms to the accent on
      hover and opens setMode('donate').

Tests (vitest, all green: 60 files / 533 tests)
  - donationSlice.test.ts: shouldShow truth table with injected `now` — grace,
    each cooldown rung, session cap, opted-out terminal, success-only.
  - GoalBar.test.jsx: renders from injected JSON, offline fallback to bundle,
    goal-met state, mini variant; plus the data module's clamp/normalize/fetch.
  - evaluateDonationPrompt.test.jsx: gating + that the postcard never fires on
    the error path (success-only contract).
  bun run typecheck:ci clean; vite build green; root bun.lock untouched
  (frozen install verified); i18n: all user-facing strings via t('donate.…')
  with English defaultValue fallbacks — no hardcoded CJK.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:59:24 +05:30