کدِ auth داخلِ App.tsx مسیرهای نسبی صدا میزند (`/api/auth/login` و مانندش).
این مسیرها به دامنهی خودِ فرانت میروند، nginx چون آن مسیر را نمیشناسد
index.html برمیگرداند، و نتیجه پاسخِ ۲۰۰ با بدنهی HTML است — یعنی res.ok
درست به نظر میرسد و بعد res.json() با «Unexpected token '<'» میشکند.
همین الان روی math.tadrisino2.ir قابلِ بازتولید است.
در حالتِ توسعه پروکسیِ vite حلش میکند: هر /api به بکاند فرستاده میشود و
کدِ موجود بدونِ هیچ تغییری کار میکند. مقصد با VITE_API_PROXY_TARGET قابلِ
عوض کردن است، پیشفرض بکاندِ مشترک.
برای بیلدِ نهایی apiUrl() اضافه شد: fetch(apiUrl('/auth/login')) بهجای
fetch('/api/auth/login'). یا اگر روی سرور مسیرِ /api به بکاند پروکسی شود،
مسیرهای نسبی همانطور که هستند درست کار میکنند — آن راه حتی بهتر است چون
same-origin میشود و CORS از میان برمیدارد.
App.tsx دست نخورده؛ تصمیمِ اینکه کدام راه را بروند با خودشان است.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
بعد از merge شدنِ PR اول، صفحههای اپ با API مقایسه شدند و چند چیز کم بود.
همهی تغییراتِ بکاند افزایشیاند، پس کدِ فعلی نمیشکند — این فقط دسترسی به
آنها را اضافه میکند.
grades() حالا lessonCount دارد: فهرستِ پایهها تا دوازدهم میرود ولی محتوا
تا نهم است، و بدونِ این عدد کاربر پایهی خالی را انتخاب میکند و صفحهی
خالی میبیند بیآنکه بفهمد چرا.
home() در هر مبحث icon و color و در هر فصل blurb میدهد، تا رنگها لازم
نباشد در کد هاردکد بمانند.
nodes.lessons(nodeId) تازه است. home() فقط شناسهی درسها را میدهد، ولی
کارتِ نقشهی مهارت عنوان، نوع (آزمون/ویدیو)، زیرنویس («۵ سوال» / «۱۲
دقیقه») و «انجام شد» میخواهد — بدونِ این، برای هر کارت یک درخواستِ intro
جدا لازم بود.
lessons.intro() متن را سهتکه میدهد: introText (توضیح)، example، و steps
(حلِ گامبهگام)؛ بهعلاوه kind و videoUrl و durationMinutes.
profile.update({ age }) هم پذیرفته میشود چون صفحهی ثبتنام سن میپرسد؛
سرور خودش به سالِ تولدِ شمسی تبدیل میکند.
هنوز هیچ فایلِ موجودی دست نخورده و پکیجی اضافه نشده. هر ۲۰ صدا در برابرِ
یک بکاندِ در حالِ اجرا تست شد.
Co-Authored-By: Claude Opus 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>