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>
9.3 KiB
تدریسینو — اپِ ریاضی (فرانتاند)
اپِ وبِ موبایلمحورِ تدریسینو، آموزشِ ریاضی به سبکِ دولینگو برای پایهی سوم تا دوازدهم: نقشهای از جزیرهها برای هر
کتاب، آزمونهای کوتاه از مسئلههای خودِ کتابِ درسی، دستیارِ هوشِ مصنوعی، همراهها، زنجیرهی روزانه و اشتراک. همین
مخزن پوستهی اندرویدِ اپ را هم دارد (android/).
بکاند مخزنِ جداگانهای است (Tadrisino/MathApp) و قراردادِ API
اسکیمای OpenAPIِ همانجاست. این اپ تنها کلاینتِ آن API است.
| سایتِ زنده | https://math.tadrisino2.ir · API: https://mathapi.tadrisino2.ir |
| فناوری | React 18 · TypeScript (strict) · Vite 6 · Tailwind 4 · Motion |
| وابستگیها | عمداً کم: بیکتابخانهی router و state؛ ناوبری و دادهی مشترک در خودِ کد |
| اندروید | پوستهی WebView روی همین سایت، با پرداختِ درونبرنامهایِ کافهبازار (Poolakey)؛ بستهی com.tadrisino.math.app |
راهاندازی
npm install
npm run dev # http://localhost:5173
در حالتِ توسعه، Vite درخواستهای /api را به بکاند پاس میدهد، پس CORS مطرح نیست. پیشفرض بکاندِ محلی است
(python manage.py runserver روی پورتِ ۸۰۰۰). بدونِ بکاندِ محلی، فایلِ .env.development.local بسازید:
VITE_DEV_PROXY_TARGET=https://mathapi.tadrisino2.ir
⚠️ در این حالت هر کاری در اپ بکنید روی دیتابیسِ واقعی نوشته میشود.
| دستور | کار |
|---|---|
npm run dev |
سرورِ توسعه |
npm run typecheck |
بررسیِ نوعها (strict) |
npm run build |
type-check و بعد build در dist/ |
npm run preview |
اجرای همان build |
ساختار
src/
config.ts آدرسِ API (بخشِ پایین)
api/ تنها درِ ارتباط با سرور — هیچ جای دیگر fetch نمیزند
http.ts توکن، timeout، تکرارِ GET، refreshِ تکنسخهای، SSE، Idempotency-Key
index.ts یک تابع برای هر اندپوینت
types.ts شکلِ پاسخها، از روی پاسخِ واقعیِ سرور
app/ App (جریانِ ورود) و MainApp (تبها و صفحهها)
nav/ ناوبریِ پشتهای، هماهنگ با دکمهی back گوشی
state/ دادهی مشترک (پروفایل، نقشه، کتابِ انتخابشده، همراه) و گفتگوی دستیار
screens/ یک فایل برای هر صفحه
components/ اجزای مشترک: مسیرِ جزیرهها، نوارِ کتابها، برگهی پایین، بازخورد، …
mascots/ همراهها (سیگما، دلتا، پی، امگا): SVG از کد، شش حالتِ چهره
diagram/ شکلِ درس و جوابِ دستیار: توصیفِ JSON از بکاند → SVG؛ همهی مختصات اینجا حساب میشود
lib/ اعدادِ فارسی، تاریخِ شمسی، MathText، پیامِ خطا، صدا، پلِ کافهبازار، روشهای پرداخت
android/ پوستهی اندروید (README خودش)
pay.html · src/pay.ts صفحهی واسطِ پرداختِ زرینپال برای اپِ اندرویدِ فعلی
قاعدهها:
- کلاینت را از حافظه ننویسید. قرارداد را از اسکیمای بکاند بخوانید
(
python manage.py spectacular --file schema.yaml --format openapi) و هر تغییرِtypes.tsرا با پاسخِ واقعیِ سرور بسنجید. - صفحهها هرگز مستقیم سرور را صدا نمیزنند: همیشه
screens/* → api/index.ts → api/http.ts. - اعداد: برای نمایش
toFa، برای ورودیdigitsOnly(کیبوردِ فارسی ارقامِ۰-۹میفرستد و\dدر JS آنها را نمیشناسد). - رنگها در
src/styles/index.css(@theme) تعریف شدهاند:bg-coral،text-ink، … - خطا: پیامِ کاربر همیشه از
friendlyErrorمیآید، نه متنِ خامِ سرور. - بازشدنِ فوری: کاربرِ واردشده خانه را از عکسِ روی گوشی میبیند (
state/homeSnapshot.ts: پروفایل، پایهها، نقشه، آمار) و همان درخواستهای بوت پشتِ صحنه تازهاش میکنند. عکس با پاک شدنِ توکن و ورودِ تازه پاک میشود، با بیلدِ دیگر یا کهنهتر از ۷ روز خوانده نمیشود، و درستی (قفل، اشتراک، قلب) همیشه با سرور است. دادهی تازهای که در خانه دیده میشود باید درAppDataبماند تا در عکس هم برود. - متنِ فارسی بیاعراب و بی «ی»ِ اضافهی نوشتهشده («پایهی سوم») است (تصمیمِ شرکت)؛ تنوین («قبلاً») میماند. متنِ تازه
را همینطور بنویسید. متنِ سرور را بکاند هنگامِ ساختنِ پاسخ پاک میکند و
plainPersianدرMathTextهمان کار را برای پاسخِ زندهی دستیار.lib/plainMath.tsهر LaTeX یا markdownی را که برسد ساده میکند و باید باcontent/plain_math.pyِ بکاند همرفتار بماند.
آدرسِ بکاند و عوض کردنِ دامنه
دامنه هیچجای کد نیامده. آدرسِ API به این ترتیب خوانده میشود (src/config.ts):
dist/config.js(ازpublic/config.js) — در زمانِ اجرا؛ بعد از build هم روی سرور ویرایششدنی است.VITE_API_BASE_URL— در زمانِ build، از.env.production./api— همدامنه، وقتی nginxِ دامنهی فرانت مسیرِ/apiرا به بکاند پراکسی کند.
برای جابهجاییِ دامنه:
- سریع، بدونِ build: روی سرور در
dist/config.jsبنویسیدapiBaseUrl: "https://<دامنهی تازه>/api"و کشِ آروان را برای همین فایل پاک کنید. - ماندگار: خطِ
VITE_API_BASE_URLدر.env.productionرا عوض کنید و build بگیرید. - سمتِ بکاند دامنهی تازهی فرانت باید مجاز باشد (
FRONTEND_URL)، و در CSPِ nginx (connect-src) دامنهی تازهی API بیاید. رویهی کامل:docs/operations.mdِ بکاند، بخشِ «عوض کردنِ دامنه».
روشهای پرداخت
برگهی پرداخت روشها را از paymentMethods در dist/config.js میخواند (src/lib/paymentMethods.ts)، جدا برای مرورگر
(web) و داخلِ اپِ اندروید (app)؛ مقدارها "zarinpal" و "bazaar". پیشفرض: مرورگر فقط زرینپال، اپ هر دو. نسخهای که
فقط در کافهبازار است: paymentMethods: { app: ["bazaar"] }؛ build لازم نیست، فقط کشِ آروانِ این فایل را پاک کنید.
انتشار
روی سرور (~/mathapp_frontend_new، که nginxِ math.tadrisino2.ir از dist/ش سرو میکند):
cd ~/mathapp_frontend_new && ./deploy.sh # fetch، npm ci، type-check، build در dist.next و جابهجاییِ بیوقفه
buildِ قبلی در dist.prev میماند (برگشت: rm -rf dist && mv dist.prev dist) و dist/config.jsِ ویرایششده روی سرور به
buildِ تازه منتقل میشود. سرور مشترکِ شرکت است: پیش از هر کار آنجا قواعدِ CLAUDE.mdِ بکاند (بخشِ «Working on the
servers») را بخوانید.
جای دیگر: npm ci && npm run build و سرو کردنِ dist/. اپ مسیرِ جدا (route) ندارد و همهچیز از / بار میشود؛ فقط
index.html و config.js نباید طولانی کش شوند (باقیِ فایلها نامِ hashدار دارند). درگاهِ پرداخت کاربر را به
/?orderId=…&status=… برمیگرداند و لینکِ دعوت /?ref=CODE است؛ هر دو را خودِ اپ میخواند.
CSPِ سایت در nginx است (ops/nginx/zareian-front-headers.conf در مخزنِ بکاند). اگر اسکریپتِ درونخطیِ index.html عوض
شود، هشِ تازهاش باید در همان فایل بنشیند، وگرنه مرورگر آن را اجرا نمیکند.