Files
clinicpro/.claude/prompt/admin-theme-dark-light-audit.md
hamed 55ab2f5dfc Implement comprehensive dark/light mode overhaul for admin panel
- Refactor color palette in `ui-design-spec.md` to utilize CSS variables exclusively, eliminating fixed hex values and Tailwind utility classes.
- Complete dark mode implementation in `uiStore.ts`, ensuring proper theme application via `applyTheme()` and `applyBrand()`.
- Create `admin-theme-dark-light-audit.md` to document the transition process, outlining issues with inline styles and fixed colors.
- Introduce `theme-tokens.test.ts` to enforce rules against fixed hex colors and ensure compliance with the design system.
- Update various components and styles to replace inline styles and fixed colors with CSS variables, ensuring consistent theming across light and dark modes.
- Ensure all changes maintain visual integrity in both light and dark modes, with a focus on accessibility and contrast standards.
2026-07-27 16:41:52 +03:30

25 KiB
Raw Permalink Blame History

اصلاح کامل لایت‌مود / دارک‌مود در پنل ادمین (SPA)

پروژه

clinicpro — فقط فرانت‌اند پنل ادمین (assets/admin/). بک‌اند و API دست نمی‌خورد.

زمینه

پنل ادمین یک design-system کامل مبتنی بر CSS variable دارد که در assets/admin/styles.css تعریف شده:

  • :root مقادیر لایت (--bg, --surface, --text, --text-2, --border, --primary, --danger, …)
  • [data-theme="dark"] همان توکن‌ها را برای دارک بازتعریف می‌کند
  • assets/admin/stores/uiStore.tsapplyTheme() روی document.documentElement هم کلاس dark و هم data-theme را ست می‌کند، پس هم dark: واریانت Tailwind و هم var(--token) هر دو فعال‌اند

مشکل این است که بخش بزرگی از صفحات این سیستم را دور می‌زنند و رنگ ثابت می‌نویسند. آمار واقعی امروز (اندازه‌گیری‌شده روی assets/admin، بدون فایل‌های تست):

الگو تعداد
style={{ color/background: ... }} ۱۰۳۷ نقطه در ~۶۰ فایل
hex ثابت داخل .tsx ۷۵۲
utility رنگ ثابت Tailwind (bg-white, text-slate-400, border-gray-100, …) ۸۸۵ در ۲۰ فایل
از این‌ها با جفت dark: ۳۲۳
کلاس‌های Tailwind با توکن (text-[var(--text-3)]) فقط ۱۵ مورد

مشکل / هدف

در دارک‌مود بخش‌هایی از پنل روشن/ناخوانا می‌مانند. سه دستهٔ خرابی، به ترتیب اهمیت:

دستهٔ A — style inline با رنگ ثابت (خرابی قطعی)

inline style قابل override با dark: نیست. در ~۹۰ نقطه دقیقاً همین اشتباه رخ داده: کنار inline style یک کلاس dark: نوشته شده که هیچ‌وقت اعمال نمی‌شود چون specificity کلاس همیشه کمتر از style attribute است.

فایل‌های آلوده به این الگوی مرده:

فایل تعداد نقطهٔ dark: مرده
components/session/PaymentStep.tsx ۲۷
components/session/DetailsStep.tsx ۲۶
components/session/CreateStep.tsx ۱۴
components/SessionPaymentAccordion.tsx ۷
components/PatientCaseBanner.tsx ۷
components/SessionServiceCard.tsx ۵
pages/SessionPaymentPage.tsx, pages/NewSessionPage.tsx, pages/EditSessionPage.tsx, components/AppointmentTurnCard.tsx ۲ هرکدام

پرمصرف‌ترین فایل‌ها از نظر کل inline style رنگی: pages/MyPatientsPage.tsx (۶۷)، pages/PatientDetailPage.tsx (۶۲)، pages/SmsWalletPage.tsx (۴۶)، components/session/PaymentStep.tsx (۴۳)، pages/SubscriptionPage.tsx (۳۱)، pages/DoctorDetailPage.tsx (۳۰)، pages/ClinicServicesPage.tsx (۲۹)، components/session/DetailsStep.tsx (۲۸)، components/session/CreateStep.tsx (۲۷)، pages/PatientsListPage.tsx (۲۶).

دستهٔ B — utility روشن بدون جفت dark:

فایل utility رنگی تعداد dark:
pages/RepresentationDetailPage.tsx ۳۳ ۱
pages/AppointmentDetailPage.tsx ۲۷ ۲
pages/PaymentDetailPage.tsx ۱۲ ۲
components/ImageCropModal.tsx ۲۳ ۹ (ناقص)

دستهٔ C — CSS ثابت در styles.css و آیکون‌های SVG

  • .inv-table thead tr { background: #e1e1e1 } و .inv-table th { color: #616161 } و .inv-table td { border-bottom: 1px solid #DBDBDB } — هیچ override .dark ندارند (فقط td border و hover دارند)
  • .inv-tabs, .inv-tab, .inv-badge.* با hex ثابت + override دستی .dark — به‌جای توکن
  • آیکون‌های SVG با stroke="#616161" / stroke="#3B3B3B" ثابت (مثلاً pages/MySecretariesPage.tsx خط ۲۶–۳۵، components/icons/) در دارک‌مود روی پس‌زمینهٔ تیره محو می‌شوند

هدف: هر رنگ سطحی/متنی/حاشیه‌ای در پنل ادمین از توکن‌های styles.css بیاید، طوری که toggle دارک‌مود بدون هیچ نقطهٔ روشن/ناخوانا کار کند — و رگرسیون بعدی با تست گارد گرفته شود.

معیار پذیرش

  • موفق: با darkMode: true (کلید clinicpro-ui در localStorage یا دکمهٔ toggle در topbar)، در هر صفحهٔ فهرست «فایل‌های مرتبط» هیچ کارت/متن/حاشیه/badge با رنگ لایت باقی نمی‌ماند؛ کنتراست متن اصلی روی --surface حداقل ۴.۵:۱ و متن ثانویه حداقل ۳:۱.
  • موفق (لایت): بعد از تغییرات، همان صفحات در لایت‌مود دقیقاً مثل قبل رندر می‌شوند — این ریفکتور نباید ظاهر لایت را عوض کند (توکن‌ها همان hexهای فعلی‌اند).
  • خطا / رگرسیون: یک تست گارد (assets/admin/test/theme-tokens.test.ts) سورس را اسکن می‌کند و اگر در فایل‌های پاک‌شده دوباره hex ثابت رنگی یا utility رنگی بدون جفت dark: اضافه شود، fail می‌شود.
  • ⚠️ مرزی ۱: تغییر brand hue (setBrandHue، مثلاً هیوی «نارنجی» که fixed دارد) در هر دو مود باید رنگ اصلی را عوض کند؛ هیچ جایی نباید #5559CE ثابت بماند.
  • ⚠️ مرزی ۲: density: compact و مرورگر قدیمی بدون پشتیبانی color-mix/oklch (بلوک @supports در styles.css) — رنگ‌ها باید روی fallback sRGB هم درست باشند، نه اینکه به transparent بیفتند.
  • ⚠️ مرزی ۳: جدول انبار (.inv-table) در دارک‌مود — thead، badgeها (in-stock/low-stock/out-of-stock) و tabها خوانا باشند.

فایل‌های مرتبط

فایل نقش
assets/admin/styles.css تعریف توکن‌ها (:root, [data-theme="dark"], @supports) + کلاس‌های .inv-* که هنوز hex ثابت دارند
assets/admin/stores/uiStore.ts applyTheme() / applyBrand() — منبع کلاس dark و data-theme و --brand-h/--brand-c
assets/admin/components/session/PaymentStep.tsx بیشترین inline style رنگی + dark:های مرده
assets/admin/components/session/DetailsStep.tsx همان الگو
assets/admin/components/session/CreateStep.tsx همان الگو
assets/admin/components/SessionPaymentAccordion.tsx, SessionServiceCard.tsx, PatientCaseBanner.tsx, AppointmentTurnCard.tsx dark:های مرده کنار inline style
assets/admin/pages/MyPatientsPage.tsx, PatientDetailPage.tsx, SmsWalletPage.tsx, SubscriptionPage.tsx, ClinicServicesPage.tsx, PatientsListPage.tsx, MySecretariesPage.tsx حجم بالای inline style رنگی
assets/admin/pages/RepresentationDetailPage.tsx, AppointmentDetailPage.tsx, PaymentDetailPage.tsx utility روشن بدون dark:
assets/admin/components/ImageCropModal.tsx جفت dark: ناقص
assets/admin/components/icons/ SVGهای با stroke/fill ثابت
docs/admin-ui/ui-design-spec.md سند UI — بخش ۱۶ «Dark Mode (اختیاری — فاز دوم)» کهنه است و با پیاده‌سازی فعلی نمی‌خواند

وضعیت فعلی

الگوی مردهٔ dark: کنار inline style — components/session/PaymentStep.tsx

// خط ۳۳–۳۵ — استایل‌های مشترک، همه hex ثابت
const fieldLabel: React.CSSProperties = { fontSize: 14, color: '#6B7280', marginBottom: 8, display: 'block' };
const primaryBtn: React.CSSProperties = { background: '#5559CE', color: '#fff', border: 'none', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
const ghostBtn: React.CSSProperties = { background: 'transparent', color: '#5559CE', border: '1px solid #5559CE', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };

// خط ۱۵۰ — کلاس dark: هیچ اثری ندارد چون inline style غالب است
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#525252' }}>{formatRial(finalPrice)}</span>

// خط ۱۵۵ — همان اشتباه روی border/background
<div className="dark:border-[#35343D]" style={{ border: '1px solid #E8EBFF', background: '#F7F8FF', borderRadius: 8, padding: 12, marginBottom: 16 }}>

utility روشن بدون جفت dark — pages/AppointmentDetailPage.tsx

// خط ۱۴۱–۱۴۲
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
  <h3 className="font-semibold text-gray-800 mb-4">اطلاعات بیمار</h3>

utility روشن بدون جفت dark — pages/RepresentationDetailPage.tsx

// خط ۲۵۱
className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-[10px] border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors"
// خط ۳۰۹
<p className="text-sm text-gray-400 text-center py-6">اطلاعات بانکی ثبت نشده</p>

CSS ثابت — assets/admin/styles.css خط ۹۵۸–۹۷۰

.inv-table { width: 100%; border-collapse: collapse; }
.inv-table thead tr { background: #e1e1e1; }          /* بدون override دارک */
.inv-table th {
  color: #616161; font-size: 14px; font-weight: 400;   /* بدون override دارک */
  padding: 10px 18px; text-align: start; white-space: nowrap;
}
.inv-table td {
  padding: 12px 18px; text-align: start; white-space: nowrap;
  font-size: 16px; font-weight: 500; color: var(--text-2);
  border-bottom: 1px solid #DBDBDB;
}
.dark .inv-table td { border-color: var(--border); }
.inv-table tbody tr:hover { background: #f4f5fd; }
.dark .inv-table tbody tr:hover { background: var(--surface-2); }

الگوی درستِ موجود در پروژه (مرجع سبک) — components/FreeVisitPrice.tsx

<p style={{ fontSize: 12, color: 'var(--text-3)', margin: '0 0 12px', lineHeight: 1.7 }}>
<span style={{ color: 'var(--danger)' }}> *</span>

این همان الگویی است که باید همه‌جا اعمال شود: inline style می‌ماند، فقط مقدار رنگ به توکن تبدیل می‌شود.

وظایف

۱. تثبیت جدول نگاشت رنگ → توکن

قبل از هر تغییر، این جدول را مبنا بگیر. hexها از شمارش واقعی فایل‌های .tsx استخراج شده‌اند (ستون «تعداد» = تکرار در سورس):

hex فعلی (لایت) تعداد hex فعلی (دارک، در dark:ها) توکن مقصد
#525252, #3b3b3b, #2f2f2f, #111827 ۴۵+۱۳+۱۶+۱۱ #d7d8ed (۷۶) var(--text)
#616161, #7e7e7e, #6b7280 ۵۱+۱۹+۳۱ #a1a1a1 (۵۰) var(--text-2)
#9ca3af ۱۰ var(--text-3)
#5559ce ۷۵ var(--primary)
#f17732, #f0753b ۳۴+۴ var(--accent)
#ffffff (سطح کارت) ۴ #222433 (۲۵) var(--surface)
#fafafa, #f1f1f1 (پس‌زمینهٔ صفحه/فیلد) ۶+۴ var(--bg) / var(--surface-2)
#efefef, #ededed, #e7e7e7 ۱۸+۴+۴ #35343d (۲۲), #343645 (۱۱) var(--border)
#e0e0e0, #e1e1e1, #dbdbdb, #d7d7d7 ۱۴+۸+… #404040 (۱۴) var(--border-2)
#ef4444, #d32f2f ۱۰+۹ var(--danger)
#2e7d32, #3c9a4f ۸+۷ var(--success)
#f59e0b ۵ var(--warning)
پس‌زمینهٔ نرم بنفش #f7f8ff, #f4f5fd, #e8ebff var(--primary-soft) / var(--primary-soft2)

نکته: بعضی نگاشت‌ها دقیقاً یکسان نیستند (#525252 در برابر --text: #3b3b3b). این اختلاف عمدی پذیرفته می‌شود چون هدف یکدست‌سازی است؛ اما اگر جایی اختلاف بصری محسوس شد (مثلاً متن اصلی کارت که واضحاً تیره‌تر/روشن‌تر می‌شود)، همان‌جا را در گزارش پایانی لیست کن به‌جای اینکه توکن جدید بسازی.

نحوه تست: جدول تصمیم است، نه کد. بعد از تعریف، در docs/admin-ui/ui-design-spec.md ثبتش کن (وظیفهٔ ۷).

۲. پاکسازی dark:های مرده (دستهٔ A، اولویت اول)

در ۱۰ فایلی که در بخش «مشکل» لیست شد: هر جا className شامل dark: کنار style={{ color|background|border|borderColor: '#…' }} است:

  1. کلاس dark: مرده را حذف کن
  2. مقدار hex داخل style را طبق جدول وظیفهٔ ۱ به var(--token) تبدیل کن
// قبل
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#525252' }}>

// بعد
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>
// قبل
<div className="dark:border-[#35343D]" style={{ border: '1px solid #E8EBFF', background: '#F7F8FF', ... }}>

// بعد
<div style={{ border: '1px solid var(--border)', background: 'var(--primary-soft)', ... }}>

ثابت‌های ماژول‌سطح (fieldLabel, primaryBtn, ghostBtn در PaymentStep.tsx) هم همین‌طور:

const fieldLabel: React.CSSProperties = { fontSize: 14, color: 'var(--text-2)', marginBottom: 8, display: 'block' };
const primaryBtn: React.CSSProperties = { background: 'var(--primary)', color: 'var(--on-primary)', ... };
const ghostBtn: React.CSSProperties = { background: 'transparent', color: 'var(--primary)', border: '1px solid var(--primary)', ... };

نحوه تست:

  • ddev exec npm run test سبز بماند (تست‌های موجود PaymentStep/SessionPaymentAccordion/… نباید بشکنند)
  • تأیید صفر شدن الگو: grep -rn "dark:" --include='*.tsx' assets/admin | grep -E "style=\{\{[^}]*(color|background)" | grep -v '\.test\.' | wc -l → باید 0 شود
  • بصری: /admin → جلسه (session) → مراحل ایجاد/جزئیات/پرداخت، یک‌بار لایت و یک‌بار دارک

۳. تبدیل بقیهٔ inline styleهای رنگی به توکن (دستهٔ A، ادامه)

فایل‌به‌فایل، به ترتیب حجم (MyPatientsPage → PatientDetailPage → SmsWalletPage → SubscriptionPage → ClinicServicesPage → PatientsListPage → MySecretariesPage → DoctorFormPage → DashboardPage → AdminSubscriptionPage → TurnsTimeline → …).

قواعد:

  • رنگ ثابت داخل stylevar(--token)
  • کلاس‌های arbitrary دوتایی مثل className="text-[#525252] dark:text-[#D7D8ED]"className="text-[var(--text)]" (یک کلاس، هر دو مود)
  • bg-[#FAFAFA] dark:bg-[#222433]bg-[var(--surface)] یا bg-[var(--bg)] بسته به اینکه سطح کارت است یا پس‌زمینهٔ فیلد
  • استثنا: hex داخل SVG که با prop رنگ می‌گیرد (مثل <PatientsGridView color={...} />) — در وظیفهٔ ۶
  • هر فایل یک commit/گام مستقل؛ فایل بعدی را قبل از تأیید بصری فایل قبلی شروع نکن

نحوه تست: بعد از هر فایل: ddev exec npm run test، سپس بازکردن صفحهٔ متناظر در /admin در هر دو مود و مقایسه با اسکرین‌شات قبلِ تغییر (لایت باید بدون تغییر باشد).

۴. اضافه کردن پوشش دارک به دستهٔ B

در pages/RepresentationDetailPage.tsx، pages/AppointmentDetailPage.tsx، pages/PaymentDetailPage.tsx، components/ImageCropModal.tsx:

utilityهای پالت ثابت را با کلاس توکنی جایگزین کن — نه اینکه جفت dark: اضافه کنی (جفت‌سازی بدهی را دو برابر می‌کند و با brand hue هم هماهنگ نمی‌شود):

// قبل
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
  <h3 className="font-semibold text-gray-800 mb-4">اطلاعات بیمار</h3>

// بعد
<div className="bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm p-6">
  <h3 className="font-semibold text-[var(--text)] mb-4">اطلاعات بیمار</h3>
// قبل (RepresentationDetailPage خط ۲۵۱)
className="… border border-gray-300 text-gray-700 hover:bg-gray-50 …"
// بعد
className="… border border-[var(--border-2)] text-[var(--text)] hover:bg-[var(--surface-2)] …"

برای رنگ‌های وضعیتی (text-red-600, border-red-300, bg-red-50) → text-[var(--danger)], border-[var(--danger)]/30, bg-[var(--danger-bg)].

نحوه تست: /admin/representations/{id}، /admin/appointments/{id}، /admin/payments/{id} و مودال کراپ تصویر (آپلود آواتار پزشک) — هر کدام در دارک و لایت.

۵. پاکسازی styles.css

  • .inv-table thead trbackground: var(--surface-2)
  • .inv-table thcolor: var(--text-2)
  • .inv-table tdborder-bottom: 1px solid var(--border) و حذف .dark .inv-table td { border-color: … } که دیگر لازم نیست
  • .inv-table tbody tr:hoverbackground: var(--surface-2) و حذف override .dark
  • .inv-tabsborder-bottom: 1px solid var(--border) و حذف .dark .inv-tabs
  • .inv-tabcolor: var(--text-2)؛ .inv-tab.activecolor: var(--primary); border-bottom: 3px solid var(--primary)؛ حذف هر دو override .dark
  • .inv-badge.in-stock|low-stock|out-of-stockbackground: var(--success-bg)|var(--warning-bg)|var(--danger-bg) و color: var(--success)|var(--warning)|var(--danger)؛ حذف سه override .dark

color: #fff روی .btn/.avatar (خطوط ۲۶۳، ۴۱۱، ۵۹۸، ۶۷۱، ۷۷۵) → var(--on-primary) هر جا روی پس‌زمینهٔ برند است؛ rgba(8,13,22,.5) اسکریم مودال (خطوط ۶۹۶، ۷۹۸) عمداً در هر دو مود یکسان است و دست نمی‌خورد — در گزارش ذکرش کن.

نحوه تست: /admin → انبار (inventory): جدول، تب‌ها و badgeهای موجودی در هر دو مود؛ ddev exec npm run build بدون خطا.

۶. آیکون‌های SVG

آیکون‌های inline با stroke="#616161" / #3B3B3B / fill="#7E7E7E" (مثلاً pages/MySecretariesPage.tsx خطوط ۲۶–۳۵ و فایل‌های components/icons/) را به stroke="currentColor" تبدیل کن و رنگ را از کلاس والد بگیر (text-[var(--text-2)]).

استثنا: آیکون‌های چندرنگ برند (stroke="#F17732" در components/icons/FilesServiceIcons.tsx، ۲۲ مورد) — این‌ها هویت رنگی دارند؛ به var(--accent) تبدیل کن، نه currentColor.

// قبل
<path d="…" stroke="#616161" strokeWidth="1.5"  />
// بعد — رنگ از والد
<span className="text-[var(--text-2)]">
  <path d="…" stroke="currentColor" strokeWidth="1.5"  />
</span>

نحوه تست: صفحهٔ «منشی‌های من» و «سرویس‌ها» در دارک‌مود — آیکون‌ها باید دیده شوند نه اینکه در پس‌زمینه گم شوند.

۷. تست گارد + به‌روزرسانی سند

۷.۱ تست گاردassets/admin/test/theme-tokens.test.ts (Vitest، همان setup موجود در assets/admin/test/):

// اسکن سورس؛ فایل‌های پاک‌شده در فاز جاری در allowlist نیستند و باید تمیز بمانند.
const CLEANED = [ /* مسیر فایل‌هایی که در وظایف ۲–۴ پاک شدند */ ];

it('فایل‌های پاک‌شده hex ثابت رنگی ندارند', () => { /* regex #rrggbb داخل style/className، به‌جز SVG برند */ });
it('الگوی مردهٔ dark: کنار inline style رنگی وجود ندارد', () => { /* کل assets/admin */ });

تست دوم باید روی کل assets/admin اجرا شود (نه فقط allowlist) چون بعد از وظیفهٔ ۲ باید صفر باشد و هرگز برنگردد.

۷.۲ سندdocs/admin-ui/ui-design-spec.md:

  • بخش ۱۶ فعلی («Dark Mode (اختیاری — فاز دوم)» با .dark { } و پالت Tailwind) با پیاده‌سازی واقعی نمی‌خواند → بازنویسی شود: دارک‌مود پیاده‌شده است، از طریق data-theme/کلاس dark روی <html> توسط uiStore.applyTheme()، و منبع رنگ فقط توکن‌های styles.css است.
  • جدول نگاشت وظیفهٔ ۱ + قاعدهٔ الزامی: «رنگ ثابت (hex یا utility پالت Tailwind) در assets/admin ممنوع؛ var(--token) یا *-[var(--token)]».
  • بخش ۱ (Color Palette) که هنوز --color-bg-sidebar: #0f172a دارد با توکن‌های واقعی هم‌خوان شود.

نحوه تست: ddev exec npm run test — گارد جدید سبز؛ برای اطمینان از اینکه واقعاً گارد است، یک‌بار موقتاً color: '#525252' به یکی از فایل‌های allowlist اضافه کن، ببین تست قرمز می‌شود، بعد برگردان.

۸. (فاز ۳ — فقط با تأیید صریح کاربر) مهاجرت سه فایل بزرگ

pages/DoctorDetailPage.tsx (۳۱۳ utility رنگی / ۱۴۲ dark:components/schedule/ScheduleSection.tsx (۲۶۰ / ۱۲۰)، pages/UserDetailPage.tsx (۱۹۸ / ۹۱).

این‌ها در دارک‌مود کار می‌کنند (جفت slate/gray + dark: دارند) ولی از design-system منحرف‌اند: با تغییر brand hue هماهنگ نمی‌شوند و رنگ خاکستری‌شان با --surface/--text مو نمی‌زند. مهاجرت ۷۷۱ نقطه ریسک رگرسیون بصری بالایی دارد و سود عملکردی فوری ندارد.

توصیه: این وظیفه را در همین پاس انجام نده. اگر کاربر تأیید کرد، فایل‌به‌فایل و با اسکرین‌شات قبل/بعد در هر دو مود.

نکات مهم

  • علت ریشه‌ای، نه علامت: الگوی className="dark:…" کنار style={{color:…}} نشان می‌دهد قبلاً تلاش شده دارک‌مود «وصله» شود بدون اینکه specificity در نظر گرفته شود. راه‌حل درست حذف رنگ ثابت است، نه اضافه کردن !important یا جفت dark: بیشتر. اگر جایی واقعاً به override نیاز شد، اول ببین آیا می‌شود کلاً از style به className رفت.
  • ترتیب اجرا اجباری است: وظیفهٔ ۲ (خرابی قطعی، ۹۰ نقطه) قبل از وظیفهٔ ۳ (حجیم). اگر بودجه/زمان تمام شد، وظایف ۲، ۴، ۵ حداقل قابل تحویل مستقل‌اند.
  • لایت‌مود نباید تغییر کند. توکن‌های لایت همان hexهای رایج‌اند؛ هر تغییر بصری محسوس در لایت یعنی نگاشت اشتباه انتخاب شده — گزارش کن، خودسرانه توکن جدید نساز.
  • توکن جدید فقط با دلیل. اگر جایی هیچ توکن مناسبی نبود، اول بررسی کن آیا واقعاً رنگ جدید لازم است یا نزدیک‌ترین توکن کافی است (guidelines §۵ — abstraction بی‌مصرف ممنوع). توکن جدید اگر اضافه شد باید در هر سه بلوک :root، [data-theme="dark"] و در صورت لزوم @supports تعریف شود.
  • سازگاری مرورگر قدیمی: بلوک @supports (color: color-mix(in oklch, red, blue)) عمداً fallback sRGB دارد (iPhone 8 / WebKit قدیمی). هیچ توکنی نباید فقط داخل @supports تعریف شود.
  • brandHue.fixed: هیوی «نارنجی» در uiStore.ts مقادیر --primary* را inline روی root می‌گذارد. یعنی هر جا #5559CE ثابت مانده باشد با انتخاب این هیو ناهماهنگ می‌شود — این یک تست بصری مستقل است، نه فقط دارک/لایت.
  • بدون تغییر backend/API. این تسک صفر تغییر در src/ و docs/api/ دارد؛ تنها سند متأثر docs/admin-ui/ui-design-spec.md است.
  • دستورها داخل ddev اجرا می‌شوند: ddev exec npm run test، ddev exec npm run build. برای مشاهدهٔ زنده ddev exec npm run watch.
  • حساب تست: 09390039833 / 09390039833 روی https://clinic-pro.ddev.site/admin.