A guest tapping a personal page — orders, transactions, profile, favourites, complete-profile — was redirected in silence, so they landed on the login box with nothing explaining what happened. Legacy showed «ابتدا وارد شوید» (henza_pwa/src/router/index.ts:259). The message is now shown in both apps. The destination deliberately stays the target's /login?redirect=…, which legacy did not have: legacy sent the guest to home and lost where they were going, while here signing in returns them to the page they asked for. The owner chose that trade-off, so it is logged as «عمداً تکرار نشد — D3/الف» rather than quietly left as a divergence. The guard also keys off meta.requiresAuth instead of legacy's hand-kept list of five route names, so the two sales-expert pages are covered too. Legacy's isLoaded router guard is NOT ported, and the reasoning is written into both apps' configStore beside the line that says so: it guarded legacy's one big bootstrap request, which this target does not have, so it would guard nothing and only add a bounce — a shared product link would open home instead of the product. That is porting the breakage, not the behaviour (D4). Route analytics is deferred at the owner's request; its design is parked in docs/PHASE2-D3-PLAN.md so it need not be re-derived, along with the two reference defects not to copy (log/create was unauthenticated and accepted any title; log/update was a GET that wrote) and the open retention and IP questions. Seven of D3's ten items turned out to be built already — all five screens in both apps, the sts_verify_step_two guard, v2's cart ceiling — verified in the code rather than from the checkmarks. Neither app has a test runner: verified with vue-tsc -b on both (clean) plus confirming the toast can actually appear, not merely compile — toast.info exists and <ToastHost /> is mounted in each App.vue. اپ: وقتی بدون ورود صفحه شخصی را باز کنید، پیام «ابتدا وارد شوید» نشان داده میشود. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.8 KiB
طرح فاز ۲ — قلم ۲ (D3): سه رفتار جامانده در اپها
تاریخ: ۱۴۰۵/۰۷/۲۰ (2026-10-12)
وضعیت — تصمیم مالک گرفته شد (۲۰۲۶/۱۰/۱۲)
«الف و ب مهم پس به ترتیب برو جلو — ج اصلا اولیوت نداره جزو اخرین چیز هایی هست که شاید درستش کنیم»
| قلم | تصمیم | وضعیت |
|---|---|---|
| الف — پیام مهمان | انجام شود | ✅ انجام شد — گزینه ب (پیام + حفظ بازگشت به مقصد) در هر دو اپ |
ب — گارد isLoaded |
انجام شود، طبق پیشنهاد پورت نشود | ✅ ثبت شد بهعنوان «عمداً تکرار نشد — D3/ب» در configStore.ts هر دو اپ |
| ج — آمار بازدید | آخرین اولویت، شاید هرگز | ⏸️ معوق — بخش «شکاف ۳» پایین بهعنوان طرحِ آماده نگه داشته شد |
چه چیزی قبلاً انجام شده (بررسی شد، ادعا نیست)
D3 ده کار بود. هفتتایش انجام شده است:
| کار | وضعیت | مدرک |
|---|---|---|
۵ صفحه /report، /report/detail، /ChildOrder، /customerChoice، /complete_profile |
✅ در هر دو اپ | apps/user-app/src/router/index.ts و همان فایل در user-app-v2 — هر پنج مسیر ثبت شده با meta.layout='FullPage' |
گارد sts_verify_step_two روی تکمیل پروفایل |
✅ | router/index.ts گارد requiresIncompleteProfile؛ از سرور میپرسد نه از حافظه، پس پروفایلی که روی دستگاه دیگر کامل شده هم محترم است |
سقف موجودی سبد v2 (capQty/maxQty) |
✅ یکسان در هر دو اپ | stores/cartStore.ts:80,88 در هر دو |
سه کار باقی است و هر سه را بررسی و در مرجع تأیید کردم.
شکاف ۱ — رفتار مهمان: الان به صفحه ورود پرتاب میشود
مرجع (henza_pwa/src/router/index.ts:253-261): کاربر واردنشده که یکی از پنج
صفحه شخصی را باز کند، یک پیام «ابتدا وارد شوید» میبیند و به خانه میرود.
if (to.name === 'ordersList' || ... || to.name === 'completeProfile') {
showNotify('ابتدا وارد شوید');
return next({name: 'home'})
} else return next()
تارگت (apps/user-app/src/router/index.ts:139-141): به /login میرود با
query.redirect، و هیچ پیامی نشان نمیدهد.
if (to.meta.requiresAuth && !session.isLoggedIn) {
return { name: 'login', query: { redirect: to.fullPath } };
}
چرا مهم است: کاربر مهمان که روی «سفارشهای من» میزند، بیهیچ توضیحی در صفحه ورود میافتد و نمیداند چه شد. رفتار مرجع صریحتر است و D3 هم همان را خواسته.
⚠️ یک تضاد که باید خودت تصمیم بگیری
رفتار فعلی تارگت از یک جهت بهتر است: query.redirect دارد، یعنی بعد از
ورود، کاربر به همان صفحهای که میخواست برمیگردد. رفتار مرجع این را ندارد —
کاربر به خانه میرود و باید دوباره دستی به آن صفحه برود.
سه گزینه:
| گزینه | نتیجه |
|---|---|
| الف) عیناً مرجع | toast + خانه. query.redirect از بین میرود. تطبیق کامل با D3. |
| ب) بهترین هر دو (پیشنهاد من) | toast «ابتدا وارد شوید» نشان داده شود، ولی مقصد همان صفحه ورود با redirect بماند. کاربر هم میفهمد چه شد، هم بعد از ورود سرِ جایش برمیگردد. در ماتریس بهعنوان «عمداً تکرار نشد» ثبت میشود. |
| ج) دست نزن | شکاف باز میماند و در ماتریس ثبت میشود. |
هزینه هر سه یکی است (چند خط). تفاوت فقط در تجربه کاربر است.
شکاف ۲ — گارد isLoaded
مرجع (router/index.ts:263-266): کاربر واردشدهای که دادههای اولیهاش
(memory.state.isLoaded) هنوز نیامده، به هر صفحهای جز home برود به خانه
برگردانده میشود — مگر آن مسیر meta.skipLoadedCheck داشته باشد.
تارگت: پورت نشده، و در کد صریحاً نوشته شده چرا:
// apps/user-app/src/stores/configStore.ts:19
// Not ported (no backend): promotionList, ignoreList, priceList, isLoaded.
نظر من: این یکی را نباید پورت کنیم، و پیشنهادم «نه» است.
دلیل: isLoaded در مرجع پرچمِ «یک درخواست بزرگ اولیه نشست» بود که تارگت ندارد —
تارگت هر صفحه دادهی خودش را میگیرد. اگر این گارد را اضافه کنیم، چیزی به کاربر
اضافه نمیشود و فقط یک پرش ناخواسته به خانه ساخته میشود: مثلاً کاربری که لینک
محصول را از تلگرام باز میکند، بهجای محصول در خانه میافتد. این دقیقاً مصداق
«باگ مرجع را پورت نکن» (D4) است.
اگر موافقی، بهعنوان «عمداً تکرار نشد — D4» ثبت میشود و D3 بسته میشود. اگر مخالفی، پورت میکنم.
شکاف ۳ — آمار بازدید صفحهها (log/create و log/update)
مرجع دو مسیر دارد و زمان ماندن کاربر در هر صفحه را اندازه میگیرد:
| مرجع | |
|---|---|
| ورود به صفحه | POST /log/create/{title} → یک ردیف با start_at، ip، title، customer_id (اگر وارد شده باشد) میسازد و id را برمیگرداند |
| خروج از صفحه | GET /log/update/{id} → end_at و time_present را پر میکند |
| صفحه محصول | data: {product_id} هم میفرستد تا بازدید هر محصول جدا شمرده شود |
| مستثنا | login و userPage::hasSubRoute |
مرجع: henza_server/app/Http/Controllers/Api/LogController.php،
henza_pwa/src/router/customAnalytics.ts.
تارگت: نه مسیر، نه جدول، نه فراخوانی. کامل غایب.
این بزرگترین قلم از سهتاست — جدول، مدل Prisma، مهاجرت، دو مسیر API، یک
composable در هر اپ، و صفحههای گزارش ادمین (queryTitle، queryAllCustomer،
queryCustomer، ago) که آمار را نشان میدهند.
⚠️ دو ایراد مرجع که عیناً پورت نمیکنم
۱. مسیر بیمحافظ است. در مرجع log/create احراز هویت ندارد و هر عنوانی را
میپذیرد، پس هر کسی از بیرون میتواند جدول را با ردیفهای جعلی پر کند و آمار
را بیمعنا یا دیسک را پر کند. پیشنهاد: عنوان فقط از یک فهرست بسته (نام
مسیرهای واقعی) پذیرفته شود و نرخ درخواست محدود شود.
۲. GET که داده را عوض میکند. log/update یک GET است که ردیف را
مینویسد. پیشنهاد: POST باشد. (اپهای تارگت تازه نوشته میشوند، پس سازگاری
عقبرو لازم نیست.)
⚠️ و یک پرسش که فقط تو میتوانی جواب بدهی
این قابلیت رفتار هر مشتری را با IP ثبت میکند — کدام صفحهها را دید و چند ثانیه ماند. فنیاش ساده است، ولی این دادهی شخصی است و مرجع هیچوقت آن را پاک نمیکرد (جدول بینهایت رشد میکند).
سه پرسش:
| پرسش | پیشنهاد من |
|---|---|
| این آمار را واقعاً لازم داری، یا در سیستم قدیم هم عملاً نگاهش نمیکردی؟ | اگر نگاهش نمیکردی، نسازیم. سادهترین قابلیت، قابلیتی است که وجود ندارد. |
| اگر لازم است، چند وقت نگه داشته شود؟ | ۹۰ روز، و قدیمیترها خودکار پاک شوند. |
| IP هم ذخیره شود؟ | نه. برای «کدام صفحه چقدر دیده شد» لازم نیست و حساسترین بخش داده است. |
جمعبندی و پیشنهاد ترتیب کار
| # | قلم | پیشنهاد من | هزینه |
|---|---|---|---|
| ۱ | toast مهمان | گزینه ب — هم پیام، هم بازگشت به صفحه مقصد | کم (نیم ساعت، هر دو اپ) |
| ۲ | گارد isLoaded |
پورت نشود — «عمداً تکرار نشد — D4» | صفر |
| ۳ | آمار بازدید | اول جواب بده لازمش داری یا نه. اگر بله: با عنوان بسته، POST، بدون IP، نگهداری ۹۰ روزه |
متوسط تا زیاد (جدول + مهاجرت + ۲ مسیر + ۲ اپ + صفحه گزارش ادمین) |
اگر بگویی «قلم ۱ و ۲ را انجام بده، ۳ را فعلاً نه»، D3 با یک ردیف «عمداً تکرار نشد» برای آمار بسته میشود و میرویم سراغ قلم بعدی فاز ۲.