- ?orderId must be numeric; every id in an API path is encodeURIComponent'd.
- The payment sheet shows success only from GET /payment/orders/{id}; a failed lookup says the
status is unknown instead of "paid".
- A 401 from /auth/refresh is not a sign-out when another tab already stored a rotated token.
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>
- profile shows "User482917" under the name, no copy button
- plan cards start with "N questions a day from Pi (free: M)" taken from the plan itself
- "ورود با حسابِ دیگر" signs out cleanly instead of keeping several accounts on one device
- "حذفِ حساب": SMS code for phone users, typing "حذف" for guests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the parents section is gone: no parent login, invite codes or "I'm a parent" button;
"گزارشِ والدین" in the profile shows the week against the previous one (praise line,
six tiles, minutes-per-day and right/wrong-per-day charts, a table of the numbers)
- birth date as day / month / year selects (Jalali, leap Esfand handled) instead of age;
adults (19+) are asked which grade's math they want to learn
- user ID under the name, copyable
- "پرداختها و فاکتورها": every order with invoice number, discount, amount and reference code
- tutor: when the free daily quota runs out, a button to the subscription page instead of "retry"
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>
اولین نسخه از روی حافظه و حدس نوشته شده بود و سه صدای مهم اصلاً کار نمیکردند.
این بار هر مسیر و هر نامِ فیلد از OpenAPI واقعیِ سرویس گرفته شد و بعد کلِ
کلاینت در برابرِ یک بکاندِ در حالِ اجرا تست شد.
چیزهایی که غلط بودند:
• ثبتِ پاسخ به /lessons/{id}/answer میرفت — چنین مسیری وجود ندارد.
مسیرِ درست /questions/{id}/answer است و فیلدش selectedOptionId، نه
questionId/choiceId.
• /ai/ask فیلدِ userQuestion میخواهد، نه question. با نامِ غلط، پرسش
اصلاً به مدل نمیرسید.
• فیدبک isHelpful بولین میخواهد، نه رشتهی up/down.
• UserProfile فیلدهای xp/coins/streak را نداشت (آنها در /user/summary
هستند) و isGuest/phone را نداشت.
• شناسهها در پاسخها رشتهاند، نه عدد.
پاسخِ دستیار هم usedChunks دارد نه sources؛ خالی بودنش یعنی از کتاب چیزی
پیدا نشده، که همان چیزیست که برچسبِ «بر اساسِ کتاب» به آن نیاز دارد.
questions.report تازه اضافه شد: بکاند حالا POST /questions/{id}/report
دارد تا کاربر بتواند ایرادِ خودِ سوال را گزارش کند.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
اپ تا الان هیچ درخواستی به سرور نمیزد و همهی دادهها داخلِ خودِ کامپوننتها
هاردکد بود. این کامیت لایهی ارتباط را اضافه میکند، بدونِ اینکه هیچ صفحهای
را تغییر دهد.
عمداً فقط فایلِ جدید اضافه شده و App.tsx دست نخورده است، چون کارِ در جریانِ
برنچِ updateApp روی همان فایل است و نباید با هم تصادف کنند. وصل کردنِ
صفحهها به این لایه کارِ بعدیست.
هیچ پکیجِ جدیدی اضافه نشد — فقط fetch استاندارد.
چیزهایی که در client.ts هست برای حالتِ خراباند، نه حالتِ خوب:
• timeout روی هر درخواست — وگرنه بکاندِ کند سوکتها را باز نگه میدارد تا
مرورگر به سقفِ ۶ درخواستِ همزمان بخورد و اپ قفل شود.
• backoff با jitter — بدونِ jitter همهی کلاینتها بعد از یک خطا همزمان
دوباره میزنند و موجِ دوم از موجِ اول بدتر است.
• تکرارِ خودکار فقط روی GET؛ POST فقط وقتی Idempotency-Key دارد (ثبتِ پاسخ،
سفارش) تا دابلکلیکِ کاربر دو بار XP یا دو سفارش نسازد.
• refresh تکنسخهای — ۲۰ درخواستِ همزمانِ ۴۰۱ نباید ۲۰ بار refresh بزنند.
• ادغامِ GETهای یکسانِ در جریان.
• احترام به هدرِ Retry-After که بکاند موقعِ throttle میفرستد.
credentials: 'omit' عمدیست: احرازِ هویت توکنِ Bearer در هدر است و سمتِ سرور
CORS_ALLOW_CREDENTIALS=False تنظیم شده.
.env هم به .gitignore اضافه شد؛ قبلاً فقط node_modules داخلش بود و آدرس/
تنظیماتِ محلی میتوانست ناخواسته کامیت شود.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>