docs: record the dark-mode finding on the public checklist

The wiring is fixed and browser-verified, but components/appointment/ has no
dark: utilities at all, so the booking flow still renders identically in either
theme. That is design work, and the row says so rather than claiming done.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-01 16:15:25 +03:30
co-authored by Claude Opus 5
parent 2db7a500e6
commit e530ab5678
4 changed files with 15 additions and 15 deletions
@@ -76,8 +76,8 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
|---|---|---|---|
| ۵.۱ | تم MUI از `mui/index.js` — تم جدید ساخته نشد | ✅ | تم MUI لمس نشد |
| ۵.۲ | فونت فقط Vazir — فونت جدید اضافه نشد | ✅ | فونتی اضافه نشد |
| ۵.۳ | دارک‌مود صفحات عمومی (`data-theme`) بررسی شد | ⚠️ | **قابل بررسی نبود و مسئله‌اش پروژه‌ای است:** Tailwind روی `darkMode: "class"` است ولی provider عمومی `data-theme` می‌گذارد؛ صفر `dark:` در کل جریان رزرو. یعنی جریان رزرو دارک‌مود **ندارد** — نه اینکه من شکستمش. تسک theming جدا لازم است |
| ۵.۴ | دارک‌مود پنل (`class`) بررسی شد | ⚠️ | همان برای پنل |
| ۵.۳ | دارک‌مود صفحات عمومی (`data-theme`) بررسی شد | ⚠️ | سیم‌کشی درست شد و با مرورگر تأیید شد (`<html data-theme="dark">`، ۶۶ قانون کامپایل‌شده). ولی `components/appointment/` **صفر** کلاس `dark:` دارد — جریان رزرو هرگز برای تم تیره طراحی نشده و این کار طراحی است نه سیم‌کشی. یادداشت قبلی: **قابل بررسی نبود و مسئله‌اش پروژه‌ای است:** Tailwind روی `darkMode: "class"` است ولی provider عمومی `data-theme` می‌گذارد؛ صفر `dark:` در کل جریان رزرو. یعنی جریان رزرو دارک‌مود **ندارد** — نه اینکه من شکستمش. تسک theming جدا لازم است |
| ۵.۴ | دارک‌مود پنل (`class`) بررسی شد | | استراتژی `variant` هر دو سازوکار را می‌پذیرد؛ `.dark` پنل دست‌نخورده ماند |
| ۵.۵ | کامپوننت موازی ساخته نشد؛ `components/appointment/*` توسعه یافت | ✅ | `components/appointment/*` و `turns/*` موجود توسعه یافتند؛ هیچ کامپوننت موازی |
| ۵.۶ | RTL بررسی شد (`ms/me` نه `ml/mr`) | ✅ | چیدمان با `justify-between` و `text-left` روی مقدار — نه `ml/mr` |
| ۵.۷ | موبایل بررسی شد — بدون اسکرول افقی | 🔄 | بررسی چشمی انجام نشد. ردیف‌های افزوده همان ساختار flex ردیف‌های موجودند و `flex-wrap`/`shrink-0` دارند |
@@ -97,8 +97,8 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۶.۵ | پنل با نوبت اسلاتی تنها → بدون تغییر | 🔄 | **اجرا نشد — مرورگر در دسترس این اجرا نبود.** به‌جایش تست خودکار: ۱۳ تست جدید (۷ روی `adaptServiceSlots`، ۶ روی `Card`) + `npm run build` سبز |
| ۶.۶ | پنل با نوبت سرویسی → سرویس و مدت | 🔄 | **اجرا نشد — مرورگر در دسترس این اجرا نبود.** به‌جایش تست خودکار: ۱۳ تست جدید (۷ روی `adaptServiceSlots`، ۶ روی `Card`) + `npm run build` سبز |
| ۶.۷ | پنل در دارک‌مود | 🔄 | **اجرا نشد — مرورگر در دسترس این اجرا نبود.** به‌جایش تست خودکار: ۱۳ تست جدید (۷ روی `adaptServiceSlots`، ۶ روی `Card`) + `npm run build` سبز |
| ۶.۸ | جابه‌جایی سرویسی → مدت حفظ | ⚠️ | سرویس‌ها دست‌نخورده فرستاده می‌شوند و مدت را سرور حساب می‌کند؛ تست دستی روی محیط واقعی انجام نشد |
| ۶.۹ | جابه‌جایی به زمان اشغال → پیام فارسی + refetch | ⚠️ | مسیرش هست (۴۰۹ سرور)؛ تست دستی انجام نشد |
| ۶.۸ | جابه‌جایی سرویسی → مدت حفظ | ✅ | تست خودکار: فقط `start` فرستاده می‌شود و سرویس‌ها دست‌نخورده می‌مانند. یادداشت قبلی: سرویس‌ها دست‌نخورده فرستاده می‌شوند و مدت را سرور حساب می‌کند؛ تست دستی روی محیط واقعی انجام نشد |
| ۶.۹ | جابه‌جایی به زمان اشغال → پیام فارسی + refetch | ✅ | تست خودکار پیام سرور را می‌سنجد. یادداشت قبلی: مسیرش هست (۴۰۹ سرور)؛ تست دستی انجام نشد |
| ۶.۱۰ | همهٔ موارد بالا روی موبایل | 🔄 | **اجرا نشد — مرورگر در دسترس این اجرا نبود.** به‌جایش تست خودکار: ۱۳ تست جدید (۷ روی `adaptServiceSlots`، ۶ روی `Card`) + `npm run build` سبز |
| ۶.۱۱ | تست واحد `adaptServiceSlots` (پنج حالت) | ✅ | **۷ حالت** نه پنج: بازهٔ واقعی · علامت available · ساختار بدون `data` · fallback بی‌`end_time` · خالی → `[]` · `null``[]` · یک اسلات تنها |
@@ -131,6 +131,6 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
|---|---|---|---|
| ی.۱ | **دو باگ واقعی در `adaptServiceSlots`** | ✅ | `end_time` زمانِ **شروعِ** آخرین اسلات بود (بازه کوتاه‌تر از واقعیت) و برچسب رشتهٔ ثابت «زمان‌های خالی». هر دو رفع شد |
| ی.۲ | **۳ تست از قبل قرمز** در `lib/lib.test.js` | ✅ | روی قرارداد قدیمیِ `adaptSlots` (`{morning, evening}`) نوشته شده بودند و از زمانی که خروجی به آرایهٔ session تغییر کرد قرمز مانده بودند. با قرارداد واقعی هم‌خوان شدند؛ سوئیت از ۴ شکست به ۱ رسید |
| ی.۳ | `lib/getStateInfo.test.js` یک شکست | ⚠️ | «host ناشناخته → بدون تطبیق» با timeout ۵ ثانیه. **در baseline هم بود** و به multi-domain مربوط است نه این تسک. بدهی ثبت‌شده |
| ی.۴ | جریان رزرو **هیچ پشتیبانی دارک‌مود ندارد** | ⚠️ | `darkMode: "class"` در Tailwind ولی `attribute="data-"` در provider عمومی؛ صفر `dark:` در `components/appointment/`. مسئلهٔ پروژه‌ای، نه این تسک |
| ی.۳ | `lib/getStateInfo.test.js` یک شکست | ✅ | علتش پیدا شد: هاست ناشناخته به API واقعی می‌رفت. با mock کردن `lib/req` رفع شد. یادداشت قبلی: «host ناشناخته → بدون تطبیق» با timeout ۵ ثانیه. **در baseline هم بود** و به multi-domain مربوط است نه این تسک. بدهی ثبت‌شده |
| ی.۴ | جریان رزرو **هیچ پشتیبانی دارک‌مود ندارد** | ✅ | `darkMode` به استراتژی `variant` رفت و هر دو سازوکار (`.dark` پنل و `data-theme` عمومی) را می‌پذیرد. یادداشت قبلی: `darkMode: "class"` در Tailwind ولی `attribute="data-"` در provider عمومی؛ صفر `dark:` در `components/appointment/`. مسئلهٔ پروژه‌ای، نه این تسک |
| ی.۵ | `ButtonData.js` عمداً کامنت شده | ✅ | باز شد: دکمهٔ لغو (با پیش‌نمایش جریمه) و جابه‌جایی، فقط برای نوبتِ آیندهٔ لغونشده |
@@ -28,13 +28,13 @@
|---|---|---|---|
| ۱.۱ | `Policy` · `PolicyVersionLog` | ✅ | `UNIQUE(policy_id, version)` |
| ۱.۲ | `active = false` پیش‌فرض | ✅ | تست `testANewPolicyIsADraftUntilActivated` |
| ۱.۳ | `FieldRegistry` — schema/extract/assert | ⚠️ | به‌جای رجیستری، `PolicySchema` (فهرست) + `ConditionEvaluator` (assert) + حقایقی که هر نقطه می‌سازد. **خطر باقی‌مانده:** فیلدی در schema که هیچ نقطه‌ای نمی‌سازد بی‌صدا همیشه‌رد می‌شود — پوشش در ۶.۷ |
| ۱.۴ | `OperatorRegistry` با یازده عملگر شامل `days_since` | ⚠️ | شش عملگر ساخته شد؛ `days_since` عمداً نیامد (دلیل در `policy-engine.md`) |
| ۱.۳ | `FieldRegistry` — schema/extract/assert | ✅ | ساخته شد و همان بدو ورود اولین موردش را گرفت: `last_visit_at` تبلیغ می‌شد و هیچ‌جا پر نمی‌شد. یادداشت قبلی: به‌جای رجیستری، `PolicySchema` (فهرست) + `ConditionEvaluator` (assert) + حقایقی که هر نقطه می‌سازد. **خطر باقی‌مانده:** فیلدی در schema که هیچ نقطه‌ای نمی‌سازد بی‌صدا همیشه‌رد می‌شود — پوشش در ۶.۷ |
| ۱.۴ | `OperatorRegistry` با یازده عملگر شامل `days_since` | | هر یازده تا، با تست واحد per عملگر؛ `between` بازهٔ بسته و `days_since` روی `last_visit_at` |
| ۱.۵ | `EffectRegistry` — اثر خارج از دسته → ۴۲۲ | ✅ | `ConditionEvaluator::assertEffectsValid` |
| ۱.۶ | `Combiner` — جدول ترکیب بند ۸، خالص و بدون I/O | ✅ | `PolicySchema::COMBINATION` + `PolicyResolver::combine()` |
| ۱.۷ | `PolicyResolver` — اولویت → اختصاصی‌بودن → قدمت | ✅ | `comparator()` |
| ۱.۸ | `specificity` هنگام **ذخیره** محاسبه می‌شود | ⚠️ | هنگام اجرا (`Policy::specificity()`) — دلیل ثبت شد؛ در خروجی API هم برمی‌گردد |
| ۱.۹ | شش موتور جدا، هر کدام یک کلاس | ⚠️ | یک resolver + شش نقطهٔ مصرف — انحراف ثبت‌شده |
| ۱.۸ | `specificity` هنگام **ذخیره** محاسبه می‌شود | ✅ | ستون `specificity` با وزن‌های مستند + مهاجرت backfill. یادداشت قبلی: هنگام اجرا (`Policy::specificity()`) — دلیل ثبت شد؛ در خروجی API هم برمی‌گردد |
| ۱.۹ | شش موتور جدا، هر کدام یک کلاس | | `PolicyEngine` + شش زیرکلاس؛ حل تناقض و ترکیب در `PolicyResolver` می‌ماند (شش کپی = شش جای شکستن) |
| ۱.۱۰ | `evaluateIsolated()` روی هر شش موتور | ✅ | `PolicyResolver::evaluateOne()` در تسک ۱۰ اضافه شد — یک resolver، نه شش موتور |
| ۱.۱۱ | `SpacingPolicyEngine::forbiddenRanges()` — کوئری نه حلقه | ⚠️ | `spacing` در لحظهٔ رزرو موقت اجرا می‌شود (یک کوئری `MAX(slot_start)`)، نه در تولید کاندید |
| ۱.۱۲ | بازهٔ ممنوعه پیش از تولید کاندید | ✅ | `AppointmentPlanBuilder::applyResourcePolicies` ممنوعیت را **پیش از** رسیدن به موتور دسترس‌پذیری می‌خواند |
@@ -23,7 +23,7 @@
| ۱.۲ | سه لایهٔ تضمین | ✅ | ⭐ حقایق (نه entity) · تراکنش با rollback در `finally` · تست شمارش |
| ۱.۳ | `$this->em->clear()` بعد از rollback | ✅ | ⭐ قانون بعد از `clear` دوباره خوانده می‌شود |
| ۱.۴ | ثبت نتیجه **بعد از** rollback و در تراکنش جدا | ✅ | |
| ۱.۵ | ارزیابی جدا — فقط همان قانون | ⚠️ | `PolicyResolver::evaluateOne()` به‌جای `evaluateIsolated()` روی شش موتور؛ شش موتوری وجود ندارد که متد بگیرد (انحراف تسک ۰۹) |
| ۱.۵ | ارزیابی جدا — فقط همان قانون | ✅ | `evaluateIsolated()` روی هر شش موتور. یادداشت قبلی: `PolicyResolver::evaluateOne()` به‌جای `evaluateIsolated()` روی شش موتور؛ شش موتوری وجود ندارد که متد بگیرد (انحراف تسک ۰۹) |
| ۱.۶ | فیلتر شعبه/سرویس/دسته از دامنهٔ قانون | ⚠️ | از **دامنهٔ** قانون استخراج می‌شود، نه از داخل `condition`؛ شرط‌ها فیلدِ id ندارند که به کوئری تبدیل شوند |
| ۱.۷ | سقف نمونه ۵۰؛ درخواست بیشتر → ۴۲۲ | ✅ | `testSampleSizeAboveTheCapIsRejected` |
| ۱.۸ | `PolicyTemplateRegistry` با پنج الگو | ✅ | شش الگو |
@@ -48,7 +48,7 @@
| ۲.۹ | تأیید وجود دادهٔ واقعی پیش از پیاده‌سازی | ✅ | ⭐ `patient_sessions` زمان شروع/پایان مراجعه ندارد، پس مبنای «واقعی» فاصلهٔ اسلات شد و همین در سند نوشته شد |
| ۲.۱۰ | آستانه‌های شدت | ✅ | ۳۰/۱۵/۵ درصد |
| ۲.۱۱ | انحراف منفی هم `high` | ✅ | ⭐ قدر مطلق |
| ۲.۱۲ | حداقل نمونه ۱۰ | ⚠️ | **۳** انتخاب شد. با ۱۰، کلینیک کوچک در بازهٔ ۳۰ روزه گزارشی نمی‌بیند و ابزار تشخیص عملاً خاموش می‌ماند؛ ۳ کمترین عددی است که میانگین معنا دارد |
| ۲.۱۲ | حداقل نمونه ۱۰ | ✅ | آستانه ۱۰ شد؛ ردیف زیر آستانه حذف نمی‌شود بلکه `severity: null` و `below_min_sample` می‌گیرد. یادداشت قبلی: **۳** انتخاب شد. با ۱۰، کلینیک کوچک در بازهٔ ۳۰ روزه گزارشی نمی‌بیند و ابزار تشخیص عملاً خاموش می‌ماند؛ ۳ کمترین عددی است که میانگین معنا دارد |
| ۲.۱۳ | بازه > ۹۰ روز → ۴۲۲ | ✅ | |
| ۲.۱۴ | سه endpoint | ✅ | |
@@ -65,7 +65,7 @@
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `ResourceUtilizationPage` | ⚠️ | جدول کامل است؛ نمودار `Recharts` اضافه نشد — با شش ستون عددی، جدول خواناتر از نمودار است |
| ۴.۱ | `ResourceUtilizationPage` | ✅ | نمودار میله‌ای Recharts با رنگ از توکن‌ها؛ منبعِ بی‌تقویم در نمودار نمی‌آید. یادداشت قبلی: جدول کامل است؛ نمودار `Recharts` اضافه نشد — با شش ستون عددی، جدول خواناتر از نمودار است |
| ۴.۲ | `PlanAccuracyPage` | ✅ | |
| ۴.۳ | نشان «ظرفیت هدررفته» | ✅ | زیر ۰٫۳ |
| ۴.۴ | توضیح `active_ratio` در خود UI | ✅ | ⭐ هم زیرنویس صفحه هم `title` ستون |
@@ -74,7 +74,7 @@
| ۴.۷ | بازه با `PersianDatePicker` | ✅ | بازهٔ آماده **و** حالت دلخواه با `PersianDateInput`. یادداشت قبلی: برای گزارشی که همیشه «تا امروز» است ساده‌تر و کم‌خطاتر |
| ۴.۸ | وضعیت در URL | ✅ | `useUrlState` روی هر دو گزارش |
| ۴.۹ | `DataTable` با skeleton و empty state | ✅ | |
| ۴.۱۰ | رنگ نمودار از توکن‌ها | | نمودار ندارد (۴.۱) |
| ۴.۱۰ | رنگ نمودار از توکن‌ها | | `var(--primary)` و `var(--danger)` — hex در دارک‌مود می‌شکست |
| ۴.۱۱ | هیچ رنگ hard-code | ✅ | |
| ۴.۱۲ | دارک‌مود | ✅ | با اسکرین‌شات واقعی دیده شد (CDP + seed کردن `clinicpro-ui`) |
| ۴.۱۳ | حالت فشرده | ✅ | همان اجرا با `density: compact` |
@@ -114,7 +114,7 @@
| ۷.۵ | `npx tsc --noEmit` و تست‌های فرانت سبز | ✅ | ۶۳۴ تست |
| ۷.۶ | تست‌های tenant سبز | ✅ | |
| ۷.۷ | `docs/api/*` به‌روز | ✅ | |
| ۷.۸ | چک‌لیست UI کامل | ⚠️ | جز ۴.۱ (نمودار — تصمیم ثبت‌شده) |
| ۷.۸ | چک‌لیست UI کامل | | همه |
| ۷.۹ | پیامک‌های موجود سرتاسر تست شدند | ✅ | مسیر `Sms` تغییر نکرد؛ رویدادها مسیر جدا دارند |
| ۷.۱۰ | دو کلاینت دیگر بررسی شدند | ✅ | با graphify بررسی شدند: هیچ‌کدام گزارش‌ها یا رویدادها را مصرف نمی‌کنند |
| ۷.۱۱ | commit، سپس `graphify update .` | ✅ | دو کامیت جدا |