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>
- 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>
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>
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>
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>
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>
- 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>
- muted text #9e9ec0 -> #727299 (contrast 2.6 -> 4.5 on white); tab labels 11px
- map: chapter titles wrap to two lines instead of being cut off
- reduced-motion respected (MotionConfig + CSS), focus-visible ring,
no pull-to-refresh, short vibration on correct/wrong answers
- src/lib/bazaar.ts: bridge to the Android WebView wrapper; checkout uses
Bazaar IAP only inside that app, Zarinpal everywhere else (unchanged)
- android/: WebView wrapper with Poolakey; written but never built
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>