# پیام‌رسان سیتکا

این سند وضعیت ماژول پیام‌رسان را پس از تکمیل ارسال پیام و
دریافت زنده (بدون رفرش) توضیح می‌دهد.

## مسیرهای API

همه پاسخ‌ها JSON هستند. مسیر پایه در نمونه توسعه:
`http://localhost/sitka/api/v1/messenger/`

| روش | مسیر | کار |
|---|---|---|
| GET | `conversations` | فهرست گفتگوهای کاربر با آخرین پیام و شمار خوانده‌نشده |
| POST | `conversations` | ساخت کانال، گروه یا گفتگوی مستقیم |
| GET | `messages?conversationId=&after=` | پیام‌های گفتگو؛ با `after` فقط پیام‌های تازه |
| POST | `messages` | ارسال پیام (`conversationId`, `body`, `clientId`) |
| GET | `sync?conversationId=&after=` | همگام‌سازی یکجا: گفتگوها، پیام‌های تازه، شمار خوانده‌نشده |
| POST | `read` | نشان‌دار کردن گفتگو به‌عنوان خوانده‌شده |

## نوار نگارش پیام

نوار نگارش در هر دو پوسته یک ساختار دارد و منطقش در
`public/assets/js/composer.js` است:

- **پیوست، وویس و ارسال هم‌اندازه و در یک راستا** می‌نشینند. در دسکتاپ
  هر سه ۴۸×۴۸ پیکسل و در موبایل ۴۲×۴۲ پیکسل با یک `border-radius` و
  `display:grid` هستند تا آیکون‌ها دقیقاً وسط بیفتند.
- **جابه‌جایی وویس و ارسال مثل تلگرام:** تا وقتی کادر خالی است دکمه
  میکروفن دیده می‌شود؛ با اولین نویسه دکمه ارسال جای آن می‌آید و با خالی
  شدن دوباره میکروفن برمی‌گردد.
- کادر نوشتن تک‌خطی شروع می‌شود و با متن رشد می‌کند (تا ۱۳۲ پیکسل)؛
  `Enter` ارسال و `Shift+Enter` خط تازه می‌سازد.
- دکمه میکروفن پیام صوتی را با `MediaRecorder` ضبط می‌کند. حین ضبط یک
  نوار روی کادر نگارش می‌آید (`.composer-rec`) با دکمه لغو، نقطه ضربان‌دار،
  زمان شمار، موج متحرک و دکمه ارسال. سقف ضبط ۵ دقیقه است.
- آپلود صدا با `XMLHttpRequest` و نوار پیشرفت (`.composer-progress` در
  پایه همان فرم) انجام می‌شود، چون `fetch` رویداد پیشرفت آپلود ندارد.

## رسانه در گفتگو (سبک تلگرام)

پردازش عکس، ویدیو و فایل در `public/assets/js/media.js` و رندرشان در
`renderAttachment` فایل `messenger.js` است:

- **عکس** در قاب گِرد با کدرِ ملایم و **دکمه دانلود شناور** روی گوشهٔ
  کارت نمایش داده می‌شود. کلیک روی خود تصویر آن را در زبانه تازه باز
  می‌کند.
- **ویدیو** با پلیر درون‌خطی (`<video controls playsinline>`) و دکمه پخش
  بزرگ روی فریم می‌آید؛ نوار بالای ویدیو نام و حجم فایل را نشان می‌دهد.
  تا وقتی کاربر پخش را شروع نکرده دکمه پخش دیده می‌شود و پس از شروع
  محو می‌گردد (`is-playing`).
- **حجم مجاز ویدیو ۲۵ مگابایت** است و پسوندهای `mp4, webm, mkv, mov, m4v, avi`
  پذیرفته می‌شوند (دسته `video` در `src/Support/Attachments.php`).
- **دانلود با انیمیشن:** کلیک روی دکمه دانلود کلاس `is-downloading` را
  روی کارت می‌گذارد، نوار پیشرفت پایین کارت پر می‌شود، دکمه دانلود
  می‌چرخد (`mediaSpinPulse`) و پس از پایان، فایل با نام اصلی ذخیره
  می‌شود. دانلود با `XMLHttpRequest` و `responseType: 'blob'` انجام
  می‌شود تا درصد پیشرفت قابل نمایش باشد.
- **آپلود با انیمیشن:** از لحظهٔ فشردن «ارسال» در پنل پیوست، یک حباب
  موقت درون گفتگو ساخته می‌شود (`.atc-upload`) با بندانگشتی فایل،
  درصد پیشرفت و فلش چرخان. پس از موفقیت، برچسب «ارسال شد» می‌گیرد،
  محو می‌شود و پیام واقعی پس از همگام‌سازی جای آن را می‌گیرد؛ در خطا
  قرمز و «ارسال نشد» می‌شود. همین کارت برای پیام صوتی نوار نگارش هم
  به کار می‌رود (`window.SitkaUploadCard`).

## نشانگر «در حال بروزرسانی…»

در سرصفحهٔ فهرست گفتگوها — هم در موبایل کنار نام سیتکا و هم در دسکتاپ —
نشانگر `.update-pill` با چرخندهٔ کوچک قرار دارد. این نشانگر:

- از همان لحظهٔ ورود روشن است تا نخستین پاسخ موفق سرور برسد.
- پس از آن، هر بار درخواستی در جریان باشد (همگام‌سازی دوره‌ای، باز کردن
  گفتگو، ارسال پیام) دوباره دیده می‌شود.
- درخواست‌های سریع‌تر از ۱۸۰ میلی‌ثانیه نشانگر را نمایان نمی‌کنند تا
  صفحه چشمک نزند؛ پنهان‌سازی هم با ۲۲۰ میلی‌ثانیه تأخیر انجام می‌شود.
- اگر به هر دلیلی داده‌ای نرسد، نشانگر پس از ۸ ثانیه جمع می‌شود.

شمارش درخواست‌ها در `beginRequest`/`endRequest` فایل `messenger.js` است.

## قفل بزرگ‌نمایی در حالت نصب

`public/assets/js/pwa.js` در حالت نصب‌شده
(`display-mode: standalone`، `fullscreen`، `minimal-ui` یا
`navigator.standalone` روی iOS) بزرگ‌نمایی را قفل می‌کند:

- `meta[name="viewport"]` به `maximum-scale=1.0, user-scalable=no` تغییر می‌کند.
- `touch-action` ریشه به `pan-y` می‌رود تا کشیدن دوانگشتی زوم نکند.
- رویدادهای `gesturestart/change/end` و لمس چندنقطه‌ای مسدود می‌شوند و
  دوضربه روی نواحی غیرتعاملی زوم نمی‌سازد (دو ضربه روی دکمه‌ها و
  ورودی‌ها همچنان کار می‌کند).
- قفل با تغییر حالت نمایش به‌صورت زنده برداشته یا اعمال می‌شود.

در مرورگر معمولی هیچ‌کدام اعمال نمی‌شود تا بزرگ‌نمایی برای دسترس‌پذیری
باز بماند.

### ساخت گفتگو

```json
{
  "type": "group",
  "title": "تیم فنی",
  "description": "هماهنگی پروژه",
  "avatar": "av-navy",
  "isPublic": true,
  "memberIds": [2, 3]
}
```

برای گفتگوی مستقیم:

```json
{ "type": "direct", "memberIds": [2] }
```

گفتگوی مستقیم تکراری ساخته نمی‌شود؛ کلید یکتای دو کاربر در
ستون `direct_key` نگه داشته می‌شود و پاسخ موجود، `created: false` دارد.

## دریافت زنده بدون رفرش

`public/assets/js/messenger.js` هر ۳٫۵ ثانیه `sync` را صدا می‌زند و:

- پیام تازه گفتگوی باز را به میز گفتگو (دسکتاپ) و نمای گفتگو (موبایل) اضافه می‌کند.
- فهرست گفتگوها، آخرین پیام و شمار خوانده‌نشده را به‌روز می‌کند.
- گفتگوی تازه‌ساخته‌شده از کاربران دیگر را بدون رفرش به فهرست اضافه می‌کند.

نکات پیاده‌سازی:

- درخواست‌های هم‌پوشان با پرچم `syncing` مسدود می‌شوند.
- با پنهان شدن تب، حلقه متوقف و با بازگشت دوباره شروع می‌شود.
- رویدادهای `online` و `offline` مرورگر مدیریت می‌شوند؛ در حالت آفلاین
  فاصله درخواست‌ها سه برابر می‌شود.
- هر گفتگو آخرین شناسه پیام را جدا نگه می‌دارد (`lastIds`).
- رندر فهرست با «اثر انگشت» مقایسه می‌شود تا بدون تغییر، صفحه دوباره ساخته نشود.

## ارسال پیام

پیام به‌صورت خوش‌بینانه (optimistic) فوراً نمایش داده می‌شود و سپس با
پاسخ سرور تأیید می‌گردد:

- در حین ارسال، حباب پیام کدر و برچسب «در حال ارسال…» دارد.
- پس از موفقیت، زمان سرور جایگزین می‌شود.
- در صورت خطا، حباب قرمز و برچسب «ارسال نشد» می‌گیرد.
- فیلد `clientId` برای تطبیق پاسخ سرور با پیام خوش‌بینانه استفاده می‌شود.

## پایگاه داده

مهاجرت `database/migrations/001_messenger.sql` چهار جدول می‌سازد:
`users`، `conversations`، `conversation_members` و `messages`.
مهاجرت `database/migrations/002_calls_and_roles.sql` جدول‌های تماس
(`call_sessions`، `call_participants`، `call_signals`) و ستون نقش عضو
(`member_role`) را اضافه می‌کند.
مهاجرت `database/migrations/003_attachments.sql` نوع پیام‌های پیوست را
گسترش می‌دهد و مهاجرت `database/migrations/004_contacts.sql` جدول‌های
`user_contacts` (فهرست مخاطبین هر کاربر) و `user_blocks` (کاربران مسدودشده)
را می‌سازد. مهاجرت `database/migrations/005_media_video.sql` مقدار
`video` را به فهرست نوع پیام‌ها اضافه می‌کند تا فیلم‌های ارسالی از
متن جدا شمرده شوند.

## مدیریت کانال و گروه

هر گفتگو یک مالک دارد و اعضا می‌توانند نقش `owner`، `admin` یا `member`
بگیرند. مالک و مدیرها می‌توانند گفتگو را ویرایش کنند، عضو بیفزایند یا
حذف کنند و نقش اعضا را تغییر دهند.

| روش | مسیر | کار |
|---|---|---|
| GET | `conversation?conversationId=` | جزئیات گفتگو، اعضا و مجوزهای کاربر |
| POST | `conversation/update` | ویرایش نام، توضیحات، نماد و عمومی بودن |
| POST | `conversation/members/add` | افزودن یک یا چند عضو |
| POST | `conversation/members/remove` | حذف عضو (مالک حذف نمی‌شود) |
| POST | `conversation/members/role` | تغییر نقش عضو (فقط مالک) |
| POST | `conversation/leave` | خروج کاربر از گفتگو |
| POST | `conversation/delete` | حذف کامل گفتگو (فقط مالک) |

مالک تا وقتی عضو دیگری در گفتگو هست نمی‌تواند خارج شود؛ باید نخست
مالکیت را منتقل کند یا گفتگو را حذف کند. رابط این بخش در
`public/assets/js/info.js` و پنل `#conversationInfo` است.

### نمایش اطلاعات گفتگو (سبک تلگرام)

با کلیک روی **آواتار یا نام گفتگو** در سرصفحه چت — هم در موبایل و هم در
دسکتاپ — پنل اطلاعات گفتگو باز می‌شود. این پنل:

- هدر رنگی با آواتار بزرگ، نام گفتگو و شمار اعضای آنلاین دارد.
- دکمه‌های کنش سریع (تماس صوتی، تماس تصویری، بی‌صدا، جست‌وجو، بیشتر و
  در گروه‌ها افزودن عضو) نشان می‌دهد.
- کارت اطلاعات با ردیف‌های نام، تعداد اعضا، نوع (گروه/کانال/مستقیم) و
  توضیحات دارد؛ ردیف توضیحات فقط وقتی متن دارد نمایش داده می‌شود.
- کلیدهای اعلان و ثابت‌کردن گفتگو در فهرست را نگه می‌دارد (تنظیم هر
  کاربر، در `localStorage`).
- فهرست اعضا با نقش، فهرست تماس‌های اخیر و بخش مدیریت را یکجا دارد.

در موبایل، دکمه بازگشت راست و پروفایل کنار آن است؛ دکمه اطلاعات جداگانه
حذف شده تا سرصفحه خلوت بماند. در دسکتاپ پنل از سمت چپ کنار میز گفتگو
باز می‌شود (چون چیدمان راست‌به‌چپ است و ستون گفتگوها سمت راست قرار دارد).
هنگام اسکرول، نوار بالای پنل پس‌زمینه روشن می‌گیرد تا روی کارت‌های سفید
خوانا بماند.

### پنل کاربر در گفتگوی خصوصی

در گفتگوی مستقیم، همان پنل به پروفایل طرف مقابل تبدیل می‌شود و
کارت‌های گروهی (اعضا، ویرایش و مدیریت) در آن پنهان می‌شوند. این تفاوت با
ویژگی `data-info-kind="direct"` روی پنل و قواعد CSS انجام می‌شود:

```
.info-panel[data-info-kind="direct"] [data-info-conversation-card],
.info-panel[data-info-kind="direct"] [data-info-members-card],
.info-panel[data-info-kind="direct"] [data-info-admin],
.info-panel[data-info-kind="direct"] [data-info-form] { display: none; }
```

ساختار این حالت، هم‌راستا با طرح مرجع:

- آواتار بزرگ، نام کاربر و خط وضعیت «آنلاین» یا «آخرین بازدید …»؛
  متن آخرین بازدید از `lastSeen` ساخته می‌شود
  (`presenceText` در `messenger.js` و `lastSeenLabel` در `info.js`).
- پنج دکمه کنش سریع در یک ردیف: تماس، تصویری، بی‌صدا/روشن، جست‌وجو،
  بیشتر. در گروه‌ها همین دکمه‌ها به‌همراه «افزودن» می‌آیند و ردیف‌ها
  می‌شکنند.
- کارت پروفایل کاربر با نام کاربری (`@کد پرسنلی`)، کد پرسنلی، سمت
  سازمانی، واحد سازمانی و نشانی رایانامه. مقدار خالی با `—` نشان داده
  می‌شود و ایمیل با جهت `ltr` رندر می‌گردد.
- فهرست اقدام‌های تخت: افزودن/حذف مخاطب، بی‌صدا کردن، جست‌وجو در گفتگو،
  مسدود کردن کاربر، پاک کردن پیام‌ها، حذف خودکار و ارسال پرونده‌های گفتگو.
- کارت «رسانه و فایل‌های اشتراکی» با پیش‌نمایش عکس‌ها و شمار رسانه، فایل
  و پیوند.
- کارت اعلان/بی‌صدا و ثابت‌کردن گفتگو در فهرست، و کارت تماس‌های اخیر.

در حالت گفتگوی خصوصی، دکمه «افزودن عضو» هرگز نمایش داده نمی‌شود؛ افزودن
عضو تنها در گروه و کانال معنا دارد.

### مخاطبین و مسدودسازی

| روش | مسیر | کار |
|---|---|---|
| POST | `messenger/contacts/toggle` | افزودن یا برداشتن کاربر از مخاطبین (`userId`) |
| POST | `messenger/contacts/block` | مسدود یا آزاد کردن کاربر (`userId`) |

این دو مسیر در `src/Http/Controllers/MessengerController.php` و روی جدول‌های
`user_contacts` و `user_blocks` (کمک‌یار `src/Support/Contacts.php`) کار
می‌کنند. برای هر جفت کاربر یک سطر یکتا نگه داشته می‌شود و حذف کاربر با
`ON DELETE CASCADE` همراه است.

پاسخ `conversation?conversationId=` در گفتگوی مستقیم، کلید `peer` را هم
برمی‌گرداند: اطلاعات عمومی کاربر، به‌همراه `isContact`، `blockedByMe` و
`blockedMe`. کلید `media` نیز رسانه و فایل‌های اشتراکی گفتگو را
(با `sharedMedia` در `src/Support/Chat.php`) می‌دهد: حداکثر ۹ عکس، ۸ فایل
و ۸ پیوند به‌همراه شمار کل هر دسته.

## حساب کاربری و حریم خصوصی و امنیت

بخش «تنظیمات» به «حساب کاربری» تغییر نام داده و ردیف «امنیت» به
«حریم خصوصی و امنیت» تبدیل شده است. ردیف تکراری «حریم خصوصی» در فهرست
موبایل با همان ردیف ادغام شده تا فهرست دو ورودی مشابه نداشته باشد.

در موبایل، ردیف «حریم خصوصی و امنیت» یک زیرصفحه تمام‌قد
(`.m-view[data-m-view="privacy"]`) باز می‌کند و در آن حالت ناوبری پایین
پنهان می‌شود. در دسکتاپ همین محتوا در `.settings-panel[data-panel="security"]`
ستون محتوای حساب کاربری نمایش داده می‌شود. هر دو نما شامل کارت امتیاز
امنیت، ورود و احراز هویت، دستگاه‌های فعال، کنترل حریم خصوصی و هشدارهای
امنیتی هستند.

کلیدهای امنیتی و انتخاب‌های حریم خصوصی (`data-sec-toggle` و
`data-sec-select`) در `localStorage` با پیشوند `sitka.security.` ذخیره
می‌شوند و بین نمای موبایل و دسکتاپ همگام می‌مانند.

## پیوست‌های پیام‌رسان

با کلیک روی دکمهٔ سنجاق در نوار نگارش (موبایل و دسکتاپ) پنل پیوست باز
می‌شود. این پنل در موبایل به‌شکل برگهٔ پایین‌کشویی و در دسکتاپ به‌صورت
پنجرهٔ وسط‌چین نمایش داده می‌شود و این گزینه‌ها را دارد:

| گزینه | نوع پیام | توضیح |
|---|---|---|
| گالری | `image` | انتخاب عکس از گالری دستگاه |
| ویدیو | `video` | انتخاب فیلم از گالری (تا ۲۵ مگابایت) |
| دوربین | `image` | گرفتن عکس با دوربین (روی موبایل) |
| فایل | `file` | سند و آرشیو (pdf, docx, xlsx, zip و …) |
| موقعیت مکانی | `location` | گرفتن مختصات فعلی با `geolocation` |
| نظرسنجی | `poll` | پرسش با چند گزینه و گزینه‌های چندگزینه‌ای/ناشناس |
| چک‌لیست | `checklist` | فهرست کارها با تیک تعاملی |
| صدا | `voice` | ضبط با `MediaRecorder` یا انتخاب فایل صوتی |
| مخاطب | `contact` | کارت تماس یکی از همکاران سازمان |
| مقاله | `article` | مطلب بلند با عنوان، خلاصه و متن |

### مسیرهای API پیوست

| روش | مسیر | کار |
|---|---|---|
| POST | `messenger/attachment/upload` | آپلود multipart عکس، ویدیو، فایل یا صدا |
| POST | `messenger/attachment/structured` | ثبت موقعیت، نظرسنجی، چک‌لیست، مخاطب و مقاله |
| GET | `/media?file=` | ارائهٔ فایل پیوست برای کاربران واردشده |

فایل‌ها در `storage/uploads` ذخیره می‌شوند و چون این پوشه بیرون ریشهٔ وب
است، از مسیر `/media` و با بررسی ورود کاربر سرو می‌شوند. نوع پیام‌های مجاز در
ستون `message_type` گسترش یافته و جزئیات هر پیوست در ستون `attachment`
(JSON) نگه داشته می‌شود؛ کلاینت آن را با کلید `meta` می‌خواند.

## تماس صوتی و تصویری

تماس‌ها با WebRTC و سیگنالینگ از طریق پایگاه داده برقرار می‌شوند
(`src/Support/Calls.php` و `src/Http/Controllers/CallController.php`).

| روش | مسیر | کار |
|---|---|---|
| POST | `calls/start` | آغاز تماس تازه و دعوت اعضای گفتگو |
| GET | `calls/state?callId=&afterSignal=` | وضعیت تماس و سیگنال‌های تازه |
| GET | `calls/incoming` | تماس فعال کاربر (برای زنگ ورودی) |
| GET | `calls/history?conversationId=` | تاریخچه تماس‌های گفتگو |
| POST | `calls/join` | پاسخ به زنگ |
| POST | `calls/decline` | رد کردن تماس |
| POST | `calls/leave` | خروج از تماس (بدون پایان دادن به کل تماس) |
| POST | `calls/end` | پایان دادن به تماس برای همه |
| POST | `calls/state` | ثبت وضعیت میکروفن، دوربین و اشتراک صفحه |
| POST | `calls/signal` | ارسال offer/answer/candidate/bye/renegotiate |

### بدون محدودیت زمانی

هیچ سقف زمانی روی تماس اعمال نمی‌شود؛ پاسخ سرور همیشه
`hasTimeLimit: false` دارد. تنها این موارد تماس را می‌بندند:

- یکی از طرف‌ها تماس را پایان دهد (`end` یا قطع در تماس دونفره).
- همه شرکت‌کنندگان از تماس خارج شوند.
- تماس در حالت «زنگ» بی‌پاسخ بماند و از ۱۲۰ ثانیه بگذرد (`no-answer`).
- شرکتی‌کننده‌ای بیش از ۴۵ ثانیه ضربان حیات نفرستد (قطع ناگهانی مرورگر).

### معماری رسانه

اتصال‌ها همتا‌به‌همتا و از نوع mesh هستند؛ هر شرکت‌کننده با بقیه یک
`RTCPeerConnection` مستقل می‌سازد. برای گروه‌های کوچک و متوسط مناسب است؛
در گروه‌های بسیار بزرگ بار شبکه و پردازنده بالا می‌رود و معماری SFU لازم
می‌شود.

قواعد پیاده‌سازی در `public/assets/js/calls.js`:

- برای جلوگیری از «glare»، فقط کاربر با شناسه کوچک‌تر پیشنهاد (offer) می‌سازد.
- هر ۴۵ ثانیه ضربان حیات ثبت می‌شود تا کاربر حذف‌شده باقی نماند.
- سیگنال‌ها پس از خواندن حذف می‌شوند تا دوباره تحویل داده نشوند.
- صفحه تماس هنگام نبود تماس پنهان است؛ زنگ ورودی با کلاس
  `is-incoming` نمایش داده می‌شود.

### محدودیت‌های مهم پیش از انتشار

1. **HTTPS یا localhost لازم است.** مرورگرها دسترسی دوربین و میکروفن را
   روی HTTP ساده (به‌جز localhost) مسدود می‌کنند. برای نصب روی دامنه یا
   IP سازمانی، گواهی SSL الزامی است.
2. **فعلاً فقط STUN تنظیم شده است.** سه سرور STUN عمومی استفاده می‌شود.
   تماس‌ها پشت NAT متقارن (بسیاری از شبکه‌های سازمانی) برقرار نمی‌شوند.
   برای محیط عملیاتی باید سرور TURN (مثلاً coturn) راه‌اندازی و در
   `RTC_CONFIG` فایل `calls.js` اضافه شود.
3. در apk/PWA، دسترسی دوربین و میکروفن باید در مانیفست و مجوزهای
   اندروید اعلام شود.

اجرای مهاجرت و داده نمونه:

```powershell
C:\xampp\php\php.exe tools/migrate.php
C:\xampp\php\php.exe tools/migrate.php --fresh
```

## اعلان‌ها

سرویس‌ورکر (`public/service-worker.php`) رویدادهای `push` و
`notificationclick` را پشتیبانی می‌کند. اگر صفحه پنهان باشد و کاربر
اجازه داده باشد، پیام تازه یک اعلان محلی می‌سازد.

درخواست‌های `/api/` از کش سرویس‌ورکر مستثنا هستند تا پیام‌ها همیشه
تازه بمانند.

## ابزارهای بررسی

```powershell
C:\xampp\php\php.exe tools/chat-api-check.php      # ۲۲ بررسی API
C:\xampp\php\php.exe tools/password-check.php      # اعتبارسنجی گذرواژه
C:\xampp\php\php.exe tools/compose-check.php       # صفحه گفتگوی جدید
C:\xampp\php\php.exe tools/calls-api-check.php     # ۴۱ بررسی API تماس و مدیریت گفتگو
C:\xampp\php\php.exe tools/attach-api-check.php     # ۳۱ بررسی API پیوست‌ها
node tools/e2e-check.mjs                           # ۱۷ بررسی رابط در مرورگر واقعی
node tools/live-chat-check.mjs                     # ۱۱ بررسی دریافت زنده
node tools/calls-ui-check.mjs                      # ۳۵ بررسی رابط پنل اطلاعات و تماس
node tools/calls-media-check.mjs                   # ۷ بررسی رسانه WebRTC بین دو مرورگر
node tools/info-direct-check.mjs                   # ۱۷ بررسی پنل کاربر در گفتگوی خصوصی
node tools/overlay-check.mjs                       # ۳ بررسی پوسته‌های شناور
node tools/composer-check.mjs                      # ۱۷ بررسی نوار نگارش، رسانه و نشانگر بروزرسانی
node tools/upload-check.mjs                        # ۸ بررسی جریان واقعی آپلود و کارت درون گفتگو
node tools/pwa-zoom-check.mjs                      # قفل بزرگ‌نمایی در حالت نصب
```
