- Text: kasra/fatha/damma/shadda/sukun/hamza-above and «هی» ezafe removed from every user-visible string (comments untouched); tanwin kept. Stale copy fixed (question packs are no longer sold), awkward onboarding sentences rewritten, سؤال → سوال. plainPersian() in MathText cleans the live tutor stream.
- Home: the mascot + speech bubble header is gone; the selected companion hops beside the current island and sits at every third bend (CSS transform animations only).
- Checkout: order summary, payment-method rows with Zarinpal/Bazaar icons and radio, amount on the pay button, lock trust line, darker overlay. paymentMethods in config.js per channel (web: zarinpal, app: zarinpal+bazaar by default).
- Profile: «ورود با حساب دیگر» removed (it was logout); new «حساب کاربری» sheet shows phone/ID and holds «حذف حساب کاربری»; logout is a red row. Delete sheet tells Bazaar subscribers to cancel renewal in Bazaar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- state/companion.ts: one store for the chosen companion (also in localStorage so splash/login show it).
BrandMark (the orange «π» tile) now shows the companion's symbol (Σ Δ π ω) in its colour; screen-header
mascots, loaders and empty/error states default to it; the assistant is «از {name} بپرس» with its face,
colour and wording; the shop, lesson and quiz «ask» buttons name it; the active tab takes its tint.
- Traits, all client-side and light: Σ sigma keeps a running tally of correct answers in the quiz header;
Δ delta shows a flame «×N» chip and an extra buzz for answers in a row; π pi gives a short solving tip
after a wrong answer; ω omega throws gold confetti at the end of every island whatever the score.
Each companion's «correct» sound is pitched differently; confetti uses its colours. Characters screen
lists each trait.
- android/README.md: what MainActivity.kt has that the installed APK lacks (vibrate bridge, media gesture,
in-app gateway + paysInApp), what to check on a phone after building, signing owner, Bazaar policy note.
Checked in headless Chrome with Sigma, Delta and Omega selected (home, assistant, shop, characters, quiz).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Shop: subscription perks shown once (each card repeated them, «۳ سوال در روز» twice), plans as selectable
rows with one big buy button, prices as «۷۹۸ هزار تومان», a single clear status card for subscribers.
Checkout inside the Bazaar app offers «کارتِ بانکی» (default) or «کافهبازار» for packs with a SKU;
«بهزودی در بازار» is gone. The gateway opens in the phone browser; status refreshes on return.
- Back button: every back-able level (open sheet, pushed screen, non-home tab, shop list) owns exactly one
history entry created right after the tap; back closes the sheet, then the screen, then returns to Home,
then leaves the app. (A re-pushed trap is skipped by Chrome's history intervention.)
- Sound: AudioContext unlocked on the first touch (it was created after a network wait, which WebView/Safari
keep muted); softer notes, option tick, finish fanfare, streak whoosh. Haptics through the new
TadrisinoAndroid.vibrate bridge when the APK has it, navigator.vibrate otherwise.
- Streak: animated flame with lit / at-risk (grey) / frozen (ice) states on Home and the streak screen,
a companion reminder when the streak is at risk or frozen, and a full-screen «روزِ پیاپی» celebration
after the first lesson of the day (needs backend 9024c6b: streak.activeToday / lost).
- Lessons: intro/example text with inline «۱. … ۲. …» renders as a numbered list (2,250 of 4,469 texts).
- Quiz: duplicate React key on the answer bubble fixed.
- android/MainActivity.kt: vibrate bridge and mediaPlaybackRequiresUserGesture=false. Not built; needs the
next APK from the signing owner.
Checked in headless Chrome against the local backend (shop as a subscriber with the Bazaar bridge stubbed,
back sequences, quiz → streak celebration, frozen state). Not checked on a real phone or in the APK.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Navigator stage: the next screen mounts hidden under the current one and is revealed only when its
first data has arrived (ScreenGate held by useAsync/useGateHold), at most 450 ms; a thin top bar shows
after 120 ms of waiting. Push slides from the left, back from the right, tabs only fade.
- Session cache (stale-while-revalidate) for screen GETs: revisited screens draw instantly.
- Home map and stats are fetched during the splash, so the first screen arrives complete.
- Entrance pop-ins (mascot, islands, lesson cards) run on first view only, not on every revisit.
- Shop status card shows placeholders instead of a wrong «نداری» while loading.
- Re-tapping the open tab scrolls to top instead of reloading; sheets close with a swipe down.
- Quiz: «۱ از ۴» instead of «۱/۴» (read as a fraction), varied praise, «N تا پشتِ هم درست»,
growth-mindset copy after a weak round, zero-reward tiles hidden.
- Streak: a concrete next step when the streak is 0 or today is still empty.
Checked in headless Chrome against the local backend (frame captures at 150 ms and 700 ms latency,
rapid tab tapping, full quiz run). Not checked on a real phone or iOS Safari.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Store opens with two choices (subscription / question packs); 3- and 6-month cards
show per-month price and saving.
- Units that need a subscription show a crown badge and open the store; a 402 from
the quiz goes there too. «ادامه بده» skips locked units.
- Profile: subscription card with an animated days-left bar (planDurationDays).
- BrandMark (SVG) on splash, home header and footer; copyright footer; floating math
glyphs on splash. CSS transform/opacity only; honours reduced motion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The wait screen counts down to the next heart second by second, fills
the first heart as time passes, shows when all five are back, and asks
the server when the time comes; 'continue' reloads the quiz. Packs and
'ask Pi meanwhile' (asking costs no hearts) are offered as ways on.
- The server now also refuses to finish a lesson/island without hearts
and answers (403 no_hearts); the quiz shows the same screen then.
- Invoices know the two admin-set statuses, canceled and refunded.
- Account deletion waits for all six digits of the SMS code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every screen but home is its own chunk and preloads when the browser is
idle; React and motion get their own long-cached chunks. The app entry
goes from 505 kB (160 kB gzip) to 85 kB (27 kB gzip).
- Quiz prompts, options and lesson examples go through MathText, so
'10 + 3 - 7 - 2' reads from the left; an unpaired bracket at the edge of
a math run stays with the Persian sentence.
- Support lists tickets from GET /support/tickets instead of this
browser's localStorage; a ticket's messages load when it is opened.
- '·' next to a Persian zero read as another digit ('فصلِ ۱ · ۰' looked
like ۱۰۰); a thin bar separates them now. Notification bodies show
Persian digits.
- A root or vertex the model already marked with coordinates is no longer
labelled a second time on top of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Home is now the island path of every unit with one clear Start button on the
current island; the six-card dashboard is gone.
- Start opens /nodes/{id}/quiz (4 questions) instead of one-question lessons, so
an island takes one round instead of ~11. Lessons stay one tap away in the sheet.
- Tutor answers render the backend's validated diagram (plane, number line,
fraction) as React SVG; function expressions go through a small parser, no eval.
- Tedi, the mobile-preview mascot, joins as the fourth companion with all six moods
and an onboarding slide.
- Light polish: 3D buttons, header gradients, warmer page background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The whole app lived in one 3,071-line App.tsx on a Figma scaffold (60 unused
packages, 45 unused UI components, no type-check). The live site could not
reach the backend, Persian-keyboard digits were erased from the phone field,
guests were signed out after an hour, "mark read" hit a method the server
does not accept (405), and iOS Safari blocked the payment window.
What changed:
- src/ split into api/ (one typed client: timeouts, GET retry, single-flight
refresh, SSE), screens/ (one file each), components/, state/, nav/, lib/.
- The API address is configuration only: public/config.js at runtime, then
VITE_API_BASE_URL at build time, then same-origin /api. No domain in code.
- Responsive shell instead of a fixed 390x844 phone frame; self-hosted
Vazirmatn instead of Google Fonts; strict TypeScript, `npm run build`
type-checks first.
- Everything on the handoff list: tutor answers keep their line breaks, have
thumbs up/down, follow-ups, "from the book" badge, retry, history that
survives tab switches and reloads, "why was I wrong" from a wrong answer
with questionId; question reports; hearts with maxHearts/unlimited and a
no-hearts screen with the refill time; result screen and a visible error
when lesson completion fails; subscription status, badge, discount code
before the gateway and the payment-return message; guest refresh token;
6-digit OTP with resend timer; age saved as a Jalali birthYear; PATCH
read-all; leaderboard refetched on open with "you" highlighted; selected
companion shown across the app; logout that revokes the refresh token.
- New screens for endpoints that had none: profile, streak calendar,
support tickets and bug reports, parents, invite-a-friend (with ?ref=
tracking), guest-to-phone upgrade.
- Browser/phone back button walks the screen stack.
Checked with a headless-Chrome walkthrough against a local backend at
390x844 and 360x740 (fresh guest: onboarding, setup, a full lesson, map,
island sheet, every tab and sub-screen, one real tutor question, reload,
logout): no console errors, no failed API calls. Real SMS login and a real
Zarinpal payment were not exercised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>