problem: ستون date نمایشی دستی پر میشد و از لحظهٔ واقعی انتشار جدا میافتاد.
- ستون published_at (timestamptz) + مهاجرت idempotent؛ مقالات منتشرشدهٔ قدیمی
از updated_at عقبگرد پر میشوند
- _apply_publish_timestamp در PUT و PATCH: در گذار draft→published مقدار
published_at ثبت میشود؛ ستون date/date_en فقط اگر خالی باشد از آن پر میشود
(تاریخ درستِ موجود را خراب نمیکند)
- پنل: فیلد «تاریخ انتشار» فقطخواندنی شد (نمایش شمسی؛ پیش از انتشار: «در لحظهٔ
انتشار ثبت میشود»)
- ArticleOut: published_at نمایان شد
باگ کاربر: در پیشنمایش فارسی مقاله، دکمهٔ EN به '/en' + اسلاگ فارسی میرفت
(«مقالهای وجود ندارد») چون اسلاگ دو زبان فرق دارد.
- LangToggle: نگاشت آگاه از اسلاگ مقاله (fa↔en از روی slug_en) — هم статье
و هم فهرست وبلاگ
- seo.ts: مسیر alternate صریح (altFa/altEn)؛ پیش از این localeHref پیشوند ساده
میساخت و hreflang درست سرور را بعد از هیدریشن خراب میکرد
- backend: /en/blog/{fa-slug} → 301 به اسلاگ انگلیسی و /blog/{en-slug} → 301
به نسخهٔ انگلیسی (جلوگیری از ۴۰۴ برای نشانیهای اشتباه)
- داده: ۱۰ مقدار slug_en که «عنوان با فاصله» بودند به اسلاگ واقعی تبدیل شدند
(مثل 'Principles of Commercial Building Design' → principles-of-commercial-...)؛
URL با فاصله هرگز کار نمیکرد
localize.ts فیلد usage را نگاشت نمیکرد و نوع Project هم آن را نداشت؛ نتیجه:
صفحهٔ /en/projects/... همچنان «مسکونی» فارسی را نشان میداد. usage و project_type
به نوع + نگاشت بومیسازی اضافه شدند.
با افزودن title_en به سه پروژه، فیلتر FEATURED_TITLES (عنوان فارسی) در نسخهٔ
انگلیسی دیگر مطابقت نمیکرد و بخش «پروژههای شاخص» بدون کارت رندر میشد.
- تطبیق با slug (زبانمستقل) + لینک کارتها با href() بومیسازی شد
- لینک نشان پروژهٔ فعال هیرو هم بومیسازی شد (در /en به نسخهٔ فارسی نمیرود)
NavLink برای «خانه» (مسیر / و /en) چون پیشوند همهٔ مسیرهاست، در هر صفحهای
active میماند (در انگلیسی واضح بود: Home و Projects همزمان روشن).
با end={item.href === '/'} دقیق شد — بقیهٔ آیتمها مثل Projects روی
صفحههای جزئیات همچنان فعال میمانند (رفتار درست).
- هدر: لوگو در موبایل h-14 (دسکتاپ h-20 مثل قبل) — ارتفاع هدر از ۱۱۲ به ۸۸ پیکسل
- PageHero: h ثابت ۵۸vh → min-h + pt-32؛ اگر محتوا (مثل سرآغاز بلند انگلیسی درباره ما)
بلندتر باشد، بخش بلندتر میشود و بردکرامب هرگز زیر لوگوی ثابت نمیرود
- Hero صفحهٔ اصلی: pt-28 تا بلوک وسطچین هم با هدر تداخل نکند؛ min-h بهجای h-screen
با iOS Safari (آیفون) تست شد
سئوی سمت سرور (backend/app/seo_pages.py)
- متا/canonical/hreflang/JSON-LD برای همهٔ صفحات عمومی (شش صفحهٔ استاتیک فارسی که قبلاً پوستهٔ خالی بودند)
- canonical سمت سرور (قبلاً فقط با JS ست میشد)، ۴۰۴ واقعی برای مسیرهای ناموجود
- اسکیما: Organization با لوگوی ImageObject، FAQPage، LocalBusiness، BreadcrumbList،
CreativeWork پروژه با همهٔ تصاویر بهصورت ImageObject (نام/توضیح + موقعیت پروژه)
- hreflang فقط وقتی ترجمهٔ انگلیسی واقعاً وجود دارد؛ sitemap با URLهای درصدگذاریشده
- llms.txt و robots.txt تازهساز از دیتابیس؛ سیاست صریح خزندههای AI + Allow: /api/site
- _site_content_html: محتوای واقعی سایت بهصورت HTML خوانا در <noscript> —
خزندههای بدون جاوااسکریپت (GPTBot/OAI-SearchBot/ClaudeBot/PerplexityBot) از ۲۴۰ نویسه
به ۸٬۵۰۰+ نویسه محتوای معنایی میرسند
- Cache-Control: no-store روی HTML و llms.txt (جلوگیری از کش کهنهٔ CDN)، max-age روی sitemap
فرانتاند
- api.tsx: خواندن دادهٔ تزریقشدهٔ سرور (window.__SITE__) بهعنوان حالت اولیه — رندر بدون XHR
- App.tsx: اسکلت بارگذاری بهجای DOM خالی (رفع Soft 404 گوگل)
- index.html: فاوآیکون واقعی برند (قبلاً به vite.svg ناموجود اشاره میکرد) + preload فونت
زیرساخت
- Dockerfile: آینهٔ PyPI (Tsinghua/Aliyun) — PyPI برای IP ایران بلاک است
- Caddyfile: هر مسیر پسونددار بدون فایل واقعی ۴۰۴ میگیرد (قبلاً HTML اسپا برمیگرداند)
- docker-compose.prod.yml: مسیرهای seo برای صفحات استاتیک + llms.txt + کلیدهای IndexNow
داده
- seed_data*.py: حذف دفاتر و معرفیهای دمو (که در هر استقرار برمیگشتند)؛
تنها دفتر واقعی: اصفهان، ملاصدرا شمالی
داراییها
- فاوآیکون/آیکونهای برند از لوگوی رسمی + کلیدهای IndexNow