هر چیزی که یک بار بیصدا خراب شد و دوباره خواهد شد. ستونِ نگهبان میگوید امروز چه چیزی میگیردش:
تلهای که نگهبان دارد اینجا برای فهمیدنِ پیامِ همان نگهبان است، و تلهای که «—» دارد فقط همین سند و
حواسِ تو را دارد — همانها در CLAUDE.md هم هستند تا پیش از چشم باشند.
جایِ این سند: فاز ۹.۳ گفته بود جدول در conventions.md بنشیند. ننشست، چون آن سند «زبان، کامیت،
کامنت» است و به انگلیسی نوشته شده تا هر تازهوارد بخواندش؛ فهرستِ تلهها نه قرارداد است نه انگلیسی.
یک سند برای یک چیز.
| تله |
نگهبان |
dist ِ کهنه: آزمون و typecheck از src میخوانند، ولی pnpm e2e، pnpm size و node apps/cli/dist/darzsaz.mjs از بیلد — پس از تغییرِ کد اول pnpm -r build، وگرنه کدِ دیروز را سبز میبینی |
— |
tsconfig.json ِ هر بسته فقط فهرستِ برنامههاست (files: [])؛ tsc --noEmit رویش هیچ نمیسنجد. tsc -p tsconfig.app.json یا pnpm typecheck |
pnpm typecheck |
await ِ سطح بالا در هر ماژولِ بستهٔ وب بهینهسازیِ تکههای rolldown را برای کل ساخت خاموش میکند (۱۴ کیلوبایت و ۲۸ پرونده در فاز ۱)؛ درون تابع async بگذار |
لینت: topLevelAwait |
pnpm run/exec با node_modules ِ ناهمگام نمیراند (verifyDepsBeforeRun: error): پس از دست زدن به package.json اول pnpm install |
خودِ pnpm |
هر pnpm install میتواند کتابخانهٔ برنامه را دو نمونه کند: @lingui/core در web و ui دو i18n شد، بی هیچ آزمونِ افتاده؛ همتا در ریشه سنجاق است |
scripts/single-instance.mjs |
اسکریپتی همنامِ فرمانِ داخلیِ pnpm با pnpm <نام> اجرا نمیشود — pnpm licenses خودِ pnpm را صدا میزند؛ نگهبان را با node scripts/… بران |
— |
pnpm --filter darzsaz ریشه را هم میگیرد — ریشه و CLI همناماند و ریشه pnpm -r میزند. CLI تنها: --filter ./apps/cli |
— |
بستهٔ ورکاسپیس در dependencies ِ CLI نه — جاسازی میشود و روی npm نیست؛ devDependencies |
scripts/release-check.mjs |
| رابط و چاپ دو فونتاند: یکانبخ فقط روی سایت؛ هر چه دانلود یا پخش میشود (گزارش چاپی، CLI روی npm) وزیرمتن — مجوزِ فونتِ تجاری پخشِ پرونده را نمیدهد |
scripts/licenses.mjs، site-fonts |
| نویسهٔ تازه در رابط ممکن است در فونتِ زیرمجموعهشده نباشد و بیصدا با فونت سامانه بیاید |
scripts/font-coverage.mjs |
pkill -f <الگو> پوستهٔ خودش را هم میکشد اگر الگو در متنِ همان فرمان باشد |
— |
| تله |
نگهبان |
متغیر CSS ِ تعریفنشده خطا نمیدهد — کلِ اعلان را بیصدا باطل میکند. توکنهای رقمدار --line-2/--panel-2/--bg-2 هستند (با خط تیره) |
scripts/css-vars.mjs |
CSS در لایه است (reset, tokens, kit, app): جای پرونده لایه را تعیین میکند و برنامه همیشه بر کیت میچربد. CSS ِ بیلایه بر همه برنده است؛ قاعدهٔ عنصر (button {}) را در @layer reset بگذار |
apps/web/vite/css.ts + بیلد |
قاعدهٔ CSS ِ کنترلِ خام پس از مهاجرت به کیت میماند و به درونِ کیت میرسد (.field input) — با مهاجرت، قاعده را هم بردار |
kit/no-raw-button، kit-usage |
min-height: 0 در ستونِ فلکسِ بیپیمایش: فرزندِ فلکس min-height: auto دارد؛ بی آن پنلِ overflow: auto کلِ صفحه را دراز میکند و پیمایش به پنجره میرود |
e2e/layout.spec.ts |
رنگِ تیره در rgba روی بوم و صحنه: زمینهٔ نیمهشفافِ سختکد در پوستهٔ روشن کنتراست را ۱٫۰۵ میکند (نوارِ سیاهِ نسل پنجم). --surface-overlay |
e2e/contrast.ts |
axe و filter: پسزمینهٔ پشت filter: brightness() را نمیبیند و کنتراست را غلط میگیرد؛ رنگ واقعی بده (--accent-hover) |
e2e/contrast.ts |
| axe روی WebGL و SVG «incomplete» میدهد و سبز میماند — سنجش با سیاه و سفیدِ مطلق در هر سه پوسته |
e2e/contrast.ts |
jsx-a11y نقش application را نااندرکنشی میداند؛ کنترل واقعی بده (دکمه)، نه میانبر پنهان روی div |
لینت: jsx-a11y |
ارقام همعرض برای ستون است، نه متن روان — tnum رقم «۱» را ۱۲۸٪ پهنتر میکند و «۳۰۰» شبیه «۳ ۰ ۰» میشود. فقط روی جدول و عددِ زنده |
— |
میانبر را با e.key نسنج — با چیدمانِ فارسی K «ن» است؛ shortcutKey(e) از apps/web/src/lib/keys.ts. میانبرِ فهرستِ راهنما بی فرمان کار نمیکند |
— |
<text textAnchor="start"> در SVG جهتآگاه است — در راستبهچپ «شروع» لبهٔ راست است؛ برچسبِ کنارِ نشان را middle کن |
— |
گزینهٔ حالتدار در منو «✓ » ِ چسبیده به برچسب نیست — section و checked در فرمان، که menuitemradio/menuitemcheckbox با aria-checked میشوند |
e2e/a11y.spec.ts |
کامپایلر React (react-hooks ۷): مقدارِ هوک را مستقیم تغییر نده؛ در r3f از attach/props استفاده کن، نه scene.x = … در افکت |
لینت: react-hooks |
immer: تابع تغییر نباید مقدار برگرداند ((p) => (p.x = v) میاندازد). بدنهٔ بلوکی |
خودِ immer، هنگام اجرا |
immer و structuredClone: تابع هستهای که structuredClone میکند (گونه، پیشنهاد ورق) را روی پیشنویس صدا نزن؛ از وضعیت ساده حساب کن و با Object.assign(draft, next) بنشان |
— |
| پنجرهٔ دوم فقط پیرو است: وضعیت را از پنجرهٔ اصلی میگیرد و هرگز نمینویسد — دو نویسنده روی یک پروژه یعنی رونویسیِ بیصدای کارِ یکی |
— |
| تله |
نگهبان |
پنل پیشنمای پنهان نقاشی نمیکند و requestAnimationFrame در تبِ پنهان اجرا نمیشود؛ عکسِ آنجا سیاه است. رندر را با darzsaz render یا pnpm e2e بسنج |
— |
عنصرِ تازهٔ three در JSX (<torusKnotGeometry>، مؤلفهٔ تازهٔ drei) کلاسش را در scene/three-catalogue.ts میخواهد — وگرنه فقط مرورگر با «not part of the THREE namespace» |
test/three-catalogue.test.ts |
EffectComposer/پسپردازش drei انباشت رندر را میشکند؛ SoftShadows با three ۰٫۱۸۵ کار نمیکند |
— |
بنچمارک perf زیر بار دستگاه ۳۵–۴۰ میلیثانیه میشود (آستانه ۳۴)؛ تنها اجرا کن: npx playwright test --project perf --no-deps |
— |
| تله |
نگهبان |
jsdom چیدمان و setPointerCapture ندارد؛ پرکنندهها در apps/web/test/setup.ts. چیزی که به چیدمان واقعی وابسته است فقط در Playwright دیده میشود |
— |
عکسِ مرجعِ آزمون تصویری فقط از کارِ دستیِ snapshots در CI میآید — رندرِ SwiftShader ِ دستگاه توسعه با تصویر رسمی یکی نیست؛ روی دستگاه آزمون کنار میرود، در CI نبودنش شکست است |
— |
mergeConfig آرایهها را به هم میچسباند: در vitest.shared.ts آرایهای که بستهها خودشان میدهند (include، exclude) نگذار |
— |
Vitest ۴ و restoreMocks: فقط vi.spyOn را برمیگرداند؛ شمار و رفتارِ vi.fn() را mockReset ِ vitest.shared.ts پاک میکند |
— |
هشدارِ act در آزمون میاندازد: تغییرِ مستقیمِ فروشگاه وقتی جزئی گوشش است، درونِ act(() => …) |
apps/web/test/setup.ts |
CSS Module در Vitest صادرهٔ نامدار ندارد مگر css.include — import * as s + s.btn بیصدا undefined میشود؛ دسترسیِ پویا (s[x]) کلاسِ مرده را از knip پنهان میکند |
— |
چکباکس و فهرستِ React Aria ورودیِ پنهان دارند؛ در Playwright روی برچسب کلیک کن، نه .check()/.selectOption() |
— |
click() ِ Playwright خودش پیمایش میکند — گزینهای که زیر لبهٔ منوی بُریده پنهان است با کلیک سبز میشود؛ «دیده میشود» را با toBeInViewport() بسنج |
— |
axe داخل iframe ِ sandbox میشکند («Target page has been closed»)؛ پیشنمای چاپ را از اسکن کنار بگذار |
— |
Playwright روی دستگاه توسعه کرومِ نصبشده را میخواهد (channel: 'chrome')؛ در CI تصویر رسمی |
scripts/test/chrome.test.mjs |
برگهٔ برچسب page label-sheet است، نه page: آزمونی که برگهها را با <section class="page"> میشمارد برچسبها را نمیبیند |
— |
جزئیاتِ هر کدام در i18n.md و conventions.md.
| تله |
نگهبان |
Lingui بیزبانِ فعال هیچچیز رندر نمیکند — نه خطا، نه هشدار، فقط DOM خالی؛ آزمونها در test/setup.ts زبان را فعال میکنند |
— |
استخراجگر Lingui فقط i18n._ و شیءِ /*i18n*/ { id, message } را میشناسد، نه کمکیهای خودت |
pnpm i18n:check |
در جزء، i18n از useLingui() و عدد از useFmt() — ثابتِ سطح ماژول با زبانِ لحظهٔ بارگذاری قفل میشود و زیرِ memo متنِ زبانِ قبل میماند |
لینت: web-i18n |
برچسب ذخیره نکن — متنی که در وضعیت میماند و بعد نشان داده میشود پیام است، نه رشتهٔ رندرشده (historyStep، errorMsg، partLabel)؛ دادهٔ کاربر با sayData استثناست |
لینت: sample-say |
پیامِ /*i18n*/ شیء است، نه متن: message ِ آن را مستقیم نخوان — فارسیِ منبع بیصدا در برگهٔ انگلیسی مینشیند |
لینت: strings |
دادهٔ کاربر یا نامِ محصول در JSX بی <bdi> در جهتِ دیگر میپیچد؛ در SVG unicodeBidi="plaintext"، و values ِ i18n._ با render(i18n, msg(D, …)) |
e2e/bidi.spec.ts، untypedValues |
exactOptionalPropertyTypes: فیلد اختیاری را با ...(x === undefined ? {} : { x }) بده، نه x: undefined |
pnpm typecheck |
| تله |
نگهبان |
اپ را اجرا کن و نگاه کن. چهار ایرادِ این نسل را هیچ آزمونی نگرفت و فقط نگاه به صفحه پیدا کرد (pnpm --filter @darzsaz/web dev) |
— |
| Prettier پیش از ویرایش خودکار: متن پرونده را همان لحظه بخوان؛ قالببندی چندخطی جایگزینیهای حدسی را بیصدا رد میکند |
— |
| هر ادعای عددی در سند با کد سنجیده میشود — مسیر، شمارهٔ خط و سقفِ اندازه |
scripts/doc-claims.mjs |