100 Commits

Author SHA1 Message Date
f7009b11e8 Instant home on reopen: snapshot of profile/grades/map/stats on the device, refreshed behind the scenes
A signed-in user with a snapshot goes straight to home; the same boot requests run in the
background and replace it. Snapshot is cleared whenever tokens are cleared and on a new login,
ignored from another build or when older than 7 days, and dropped when the fresh profile is a
different user (reboot remounts MainApp via a generation key; AnimatePresence otherwise kept the
exiting one). Notifications/characters wait for the profile answer so an expired access token
costs the same requests as before. refreshHome applies only the latest answer. Home shows a
"saved data" strip when the map could not be refreshed.

Measured locally (old vs new build, same backend, Chrome throttling, median of 3, time to map):
Fast 3G 2.46s -> 1.80s (expired token 3.63s -> 1.80s); Slow 3G 8.52s -> 6.14s (expired 12.6s ->
6.13s); API requests per open unchanged (6 valid, 11 expired). 12/12 behaviour checks passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:57:42 +03:30
f0b7a5e28f Diagrams: hide answer-giving counts in quizzes (HideCounts); bar/pie slices take the colour named by their label
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 21:27:22 +03:30
24a6f7c570 Quiz: 'خروج' leaves the quiz even though the confirm sheet holds a back step (nav.leave)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 02:18:10 +03:30
2e7e5823cc lineFit runs before first paint (no 150ms debounce): no flash of unfitted text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:59:03 +03:30
09f6b78198 Text rendering: unpaired closing paren returns to the sentence, formulas in one sentence stay inline, matrix rows align as a grid, line fit also for short items and drops justify when gaps stay ugly
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:53:27 +03:30
22bdb4d39f Line fitting for justified text: per-paragraph word-spacing chosen by measured gap badness (Knuth-Plass-style shrink)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:31:52 +03:30
f214abff3c plainMath: vdots/ddots (with or without backslash) and sqrt( no longer reach the student raw
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:24:09 +03:30
9a6ef69480 Justify multi-line prose app-wide (p, li, pre-line text); centered sections keep centering
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 13:09:26 +03:30
12c3721d92 diagram: kind=space (fixed oblique 3D view)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 01:04:08 +03:30
782998e325 MathText: matrix [[..],[..]] drawn as a real matrix block instead of wrapping mid-row
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 22:13:34 +03:30
2ac783d222 Brand name shown as «تدریسینو ریاضی» everywhere users see it (splash, login, onboarding, footer, subscription, share); splash nowrap + balanced titles for narrow phones
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 21:55:20 +03:30
941e8245f1 MathText display: formula in the middle of a Persian sentence gets its own centered line (quiz and lessons)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:24:55 +03:30
3a0192518c README rewritten as a formal entry page for the current app; Android version note corrected
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:47:55 +03:30
ca96a91dee No diacritics or written ezafe ی in the UI; map without the speech banner, companions cheering along the path; familiar payment sheet with gateway logos and per-channel methods; account deletion moved under «حساب کاربری»
- 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>
2026-10-14 16:55:17 +03:30
4a661f504f Offline wording back to "اینترنت قطع است / کند است" (owner's choice)
This reverts commit beeada6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:07:18 +03:30
beeada6814 Offline wording: "اینترنت متصل نیست" under the app's splash image (Android) and in web error messages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:05:53 +03:30
872e272ee1 Bazaar: clear message when Bazaar is missing, old or not connected; reconnect before the next try
Android: buy() checks the Poolakey connection state and reconnects; BazaarNotFound / not supported / dynamic price
not supported become error "unavailable" (not_installed | old_bazaar | not_connected) instead of a generic failure.
assembleDebug green: com.tadrisino.math.app, versionCode 2, queries com.farsitel.bazaar. Not run on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:56:54 +03:30
854149bb5d Subscriptions only: no question credits anywhere; Bazaar bridge is subscribeProduct only (bazaarBuy(sku, orderId, token), bazaarPrices(skusJson)), no consume
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:33:47 +03:30
e0a9605b8e Store sells subscriptions only: each card lists what you get (features + assistant questions/day), tab renamed, assistant points to subscription
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:57:18 +03:30
d440986b88 Checkout: Zarinpal is the default; Bazaar tile shows the price Bazaar itself has (Poolakey getSkuDetails bridge)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:21:48 +03:30
fffdeac3ed Bazaar: subscriptions (subscribeProduct), dynamic price token, RSA key; Zarinpal and Bazaar side by side at checkout
- Checkout sheet always shows both methods as two tiles; Bazaar tile disabled with the reason
  (not in Bazaar / Android app only / update the app) instead of hidden; default is Bazaar inside an app that can buy.
- Bridge v2: TadrisinoAndroid.bazaarBuy(sku, orderId, productType, dynamicPriceToken); inAndroidApp() split
  from canBuyFromBazaar() so the pay.html workaround still applies to the installed APK.
- Bazaar flow: order -> optional discount code -> POST /payment/bazaar/orders/{id}/start -> Poolakey -> verify;
  consume only packs.
- Android: RSA key, subscribeProduct/getSubscribedProducts for subscriptions, dynamicPriceToken, versionCode 2.
  compileDebugKotlin green; no purchase tested on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:51:03 +03:30
3a2a685ed3 FeedbackBar: like/dislike/report under every quiz question and lesson 2026-10-12 12:56:52 +03:30
41f1c993ee plainMath: identifiers like OPENAI_API_KEY untouched, log base text 2026-10-12 12:18:48 +03:30
f2263e6dff MathText: last-line guard that turns stray LaTeX/markdown into plain Unicode 2026-10-12 12:15:31 +03:30
cf2a3251dd Quiz: draw the question figure under the prompt (Question.figure) 2026-10-12 11:58:42 +03:30
18eeb54327 Island label shows up to 3 lines so the part number of split islands stays visible
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 10:54:34 +03:30
f569ece55b Subject bar under the header in Quizzes and Lessons, synced between tabs; bidi fix for Latin math in RTL text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 05:35:30 +03:30
56dcc93202 android/README: splash/offline are the APK's job; acceptance checklist incl. VPN-on white page 2026-10-11 22:57:57 +03:30
186dc208be GET requests time out after 8s (was 15s): the 'internet is slow' message and retry come sooner
Writes keep 15s so an early cut does not make the user submit twice. Worst-case boot
wait with an unreachable API goes from ~50s to ~27s (3 attempts + backoff). Not tested
on a real slow network.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:54:21 +03:30
b635b15c7d android/README: splash and offline screen join the next-APK list 2026-10-11 22:52:17 +03:30
0d6396e0de Users never see error text: offline/slow wording in app and web; technical details are never shown
friendlyError now shows a server detail only when it is Persian prose (no 'خطای 404', no
DRF English defaults); network errors say 'اینترنت قطع است' (navigator offline) or
'اینترنت کند است' (timeout). The native connecting screen picks the same two wordings from
ConnectivityManager (ACCESS_NETWORK_STATE). Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:50:58 +03:30
ab02bb43b9 Native splash held while connecting; offline shows the same picture with a connecting line; web splash hidden inside the app
Inside the app the web splash is invisible with the new APK (the system splash is still on
top) and icon-only for old APKs, so there is no second picture to switch to. Offline or a
5xx on the main frame: system splash released onto the same icon (288dp drawable) with
'در حال اتصال به اینترنت…', retried every 5s. A slow-but-online load shows nothing extra.
Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:48:06 +03:30
446cf36103 Keep the system splash until the first real screen: no blank WebView gap, no second splash
Installed APK sequence was system splash -> white WebView -> web splash -> home. The
activity now holds the system splash (core-splashscreen) until the page calls
TadrisinoAndroid.appReady(), after its first non-boot screen; released after 10s or on a
main-frame load error so a dead site shows the WebView error, not an endless splash.
Older APKs have no appReady and are unaffected. Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:41:07 +03:30
3520279143 One static splash: app icon + name in index.html from the first frame, no companion, no timers
The first second was a blank page, then a splash with the selected companion, then the
home screen. Now index.html carries the splash (icon from the Android launcher, name,
white to match the native window background) and SplashScreen draws the same markup, so
the hand-over from HTML to React shows no change. Not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:33:02 +03:30
16cf5c91f7 Spread retries over 1-2x Retry-After so a crowd turned away together does not return together
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:55:29 +03:30
c7407f32b9 Splash only appears when the server is slow: no flash of the welcome screen on a fast start
The splash used to stay a fixed 700ms plus a fade, so a fast response still showed it
briefly and jumped away. Now only the matching background shows for the first 350ms;
the splash content appears only if the response has not arrived by then, and once shown
it stays at least 700ms. Not run on a device.
2026-10-11 01:19:34 +03:30
8a61cb25b8 The selected companion everywhere, each with a small trait; next-APK notes
- 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>
2026-10-10 18:51:19 +03:30
737414f246 Payment from the Android app: reach Zarinpal through pay.html so it carries our Referer
The app hands every outside URL to the phone browser, which sends no Referer, and Zarinpal's StartPay
refuses a request without our domain as Referer («دسترسی از این دامنه مجاز نمی‌باشد»). Checked against
production on a pending order: no Referer → refused; Referer https://math.tadrisino2.ir/ → forwards to
the bank (sep.shaparak.ir).

- pay.html + src/pay.ts: opened in the phone browser, forwards only to our own
  /api/payment/orders/<id>/pay link (anything else is refused), so the Referer is math.tadrisino2.ir.
  The app opens it as http:// (the WebView keeps only https on its own host); ArvanCloud moves it to
  https, and the link rides in the fragment, which never reaches a server.
- MainActivity.kt (next APK): payment pages (our subdomains, zarinpal, shaparak) stay inside the WebView
  and `paysInApp()` tells the frontend to navigate directly. Not built.

Not checked: a full payment on a real phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:40:10 +03:30
468f5b2d29 Calmer shop with payment choice, step-by-step phone back, sound/haptics that reach the phone, live streak flame
- 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>
2026-10-10 18:27:24 +03:30
eafee76a93 Smooth screen transitions: new screen waits hidden until its data arrives, no half-built flash
- 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>
2026-10-10 17:55:47 +03:30
19ca3d7de1 Merge personal-fork Android build commit; keep MH-W90's signing flow from upstream (owner of Android signing)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:41:20 +03:30
f74b6ad279 Companions: each wears its own Greek letter badge (Δ, π, ω); Tedi renamed Omega (ω)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:40:52 +03:30
a8a5351862 Subscription store, paywall on locked units, profile subscription bar, Tadrisino branding
- 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>
2026-10-10 01:19:09 +03:30
8879b42b1c Fix: first screen's looping animations (mascot bob, island ring, start button) never ran until a tab switch; AnimatePresence initial={false} leaked into children 2026-10-08 18:12:35 +03:30
6930c83e07 Quiz: selecting an option only highlights it blue; the answer is submitted by a «بررسی» button and green/red is shown after, then «ادامه» 2026-10-08 18:04:02 +03:30
d0a2822658 Fine-tune: bigger invisible tap areas on header chips, press feedback on tabs and chips, touch-action manipulation
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:59:12 +03:30
50f6ade9f8 Replace keyboard emojis with the app's own lucide icons (quiz reactions, +XP, welcome hand, party popper, streak flame)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:47:05 +03:30
22fc870176 Smoother tab switch (no dark flash), full welcome sentence, chapter banner sticky and chip centered, floating math glyphs in headers, quiz reaction emojis
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:38:28 +03:30
54b872026d Lessons and packages headers: back to the owner's preferred colors (green, coral), exactly as before
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:31:01 +03:30
54bd865835 Colors keep their role: headers/banners darken to readable contrast (same hue), lessons uses the teal token, packages use indigo; +XP pop, streak milestone card, lazy confetti
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:26:07 +03:30