تصور کنید در یک وبلاگ سادهی وردپرسی، بازدیدکنندگان بتوانند اشیا را در صفحه جابهجا کنند و دیگران این تغییرات را بهصورت زنده ببینند. این اتفاق اکنون در سایت dhseadev.online رخ داده است و ثابت میکند که وبلاگهای استاتیک میتوانند به فضاهای چندنفره تبدیل شوند.
طبق گزارش توسعهدهندهی این پروژه، از ۸ اوت ۲۰۲۶، قابلیتهایی مثل «اتاقهای بازی مشترک»، «دیوار مثبتاندیشی» و «سیستم تأیید خواندن زنده برای هر پست» به این سایت اضافه شده است. این تحول با استفاده از playhtml — کتابخانهای ساخته شده توسط اسپنسر چانگ — ممکن شده است. این ابزار به وبلاگهای مدیریتشده در WordPress.com اجازه میدهد بدون نیاز به افزونه یا بکاِند اختصاصی، مانند یک فضای چندنفره (Multiplayer) عمل کنند.
بیشتر محیطهای میزبانی مدیریتشده برای تحویل محتوای استاتیک طراحی شدهاند و برای تعاملات زنده مناسب نیستند. بهطور سنتی، افزودن ویژگیهای چندنفره نیازمند سرور اختصاصی، پایگاهداده برای مدیریت وضعیت (State Management) و تنظیمات پیچیدهی WebSocket است. این ساختار معمولاً با محدودیتهای پلتفرمهایی مثل WordPress.com که اجازه اجرای کد سمت سرور را نمیدهند، در تضاد است.
playhtml این موانع را با استفاده از یک اسکریپت CDN و ویژگیهای داده (Data Attributes) برای همگامسازی عناصر HTML دور میزند. این کتابخانه از PartyKit برای مدیریت یک سند JSON کوچک و مشترک برای هر عنصر استفاده میکند که کلید آن URL صفحه است. به این معنا که هر بازدیدکنندهای که وارد یک URL خاص میشود، وارد یک «اتاق» منحصربهفرد میشود و هر اقدامی — مثل کشیدن یک شیء — فوراً برای دیگران قابل مشاهده است.
همانطور که در تحلیلهای قبلی ما دربارهی محدودیتهای پلتفرمهای بسته اشاره کردیم، یافتن راهکارهای دور زدن (Bypass) برای افزودن قابلیتهای مدرن، همواره جذابیت فنی بالایی دارد.
مکانیزم پیادهسازی
توسعهدهنده چندین ویژگی تعاملی را با استفاده از ویژگیهای خاص این کتابخانه پیاده کرده است:
can-move: به بازدیدکنندگان اجازه میدهد آهنرباهای گرافیکی را در فضای مشترک جابهجا کنند. همه جابهجاییها را بهصورت زنده میبینند و موقعیت نهایی در سرور ذخیره میشود تا برای بازدیدکنندگان بعدی نیز باقی بماند.can-spin،can-growوcan-duplicate: تغییرات collaborative مختلفی را ایجاد میکنند که دقیقاً مطابق نامشان عمل میکنند (چرخاندن، بزرگ کردن و تکثیر عناصر).can-play: یک درگاه برای منطقهای سفارشی است. این ویژگی به یک شیءdefaultDataبرای مقداردهی اولیه، یک هندلر رویداد که تابعsetDataرا فراخوانی میکند و یک رندرکنندهupdateElementبرای همگامسازی وضعیت بین مرورگرها نیاز دارد.
زمینه: قدرت اتاقهای مبتنی بر URL
مدل ذهنی playhtml بر اساس یک سند JSON مشترک برای هر عنصر است که با URL صفحه کلیدگذاری شده است. اگرچه در ابتدا این موضوع شبیه به یک محدودیت به نظر میرسد، اما در واقع یک «ابر-قدرت» است. چون playhtml وضعیت مشترک را به صفحهای که در آن قرار دارد محدود میکند، انتقال یک عنصر به یک URL متفاوت، یک اتاق تازه و خالی ایجاد میکند؛ در این حالت وضعیت قدیمی به جای انتقال، رها (Orphaned) میشود.
این معماری باعث میشود یک اسکریپت واحد که در کل سایت تزریق شده، بهطور خودکار برای هر صفحه به یک ویژگی مجزا تبدیل شود. برای مثال، کارت «تأیید خواندن» (Read-receipts) یک بلوک واحد در قالب فوتر سایت است. این کارت بررسی میکند که آیا کلاس single-post وردپرس در بدنه صفحه وجود دارد یا خیر و در صورت وجود، یک کارت مهر زدن را در انتهای مقاله تزریق میکند. چون اتاقها بر اساس URL هستند، هر پست — چه قدیمی و چه جدید — بدون نیاز به هیچگونه ویرایش دستی در هر پست، شمارندهی تأیید خواندن مستقل خود را دارد. در واقع، انتشار یک پست جدید، بهطور خودکار آن را به این سیستم متصل میکند.
جزئیات منطق تأیید خواندن
این منطق از حدود ۴۰ خط کد با مکانیزمهای زیر تشکیل شده است:
- مقداردهی اولیه: استفاده از
defaultData: {stamps: []}برای تعریف لیست مهرهای اولیه. - تعامل: هر کلیک، یک شیء شامل یک بذر (Seed) و یک برچسب زمانی
{s: seed, t: timestamp}را به لیست اضافه میکند. - حذف تکرار: یک بذر مخصوص هر مرورگر در
localStorageذخیره میشود تا اطمینان حاصل شود که هر کاربر فقط یکبار میتواند مهر بزند. - بهینهسازی: برای حفظ عملکرد و سرعت، سقف ۹۶ مهر تعیین شده است و با رسیدن به این تعداد، قدیمیترین ورودیها حذف میشوند.
تنها دلیل اثبات موفقیت این سیستم، تست بازگذاری مجدد (Reload Test) است: کلیک روی دکمه، رفرش کردن صفحه و مشاهدهی اینکه تعداد مهرها حفظ شده است. این موضوع تأیید میکند که وضعیت دادهها از مرورگر به PartyKit رفته و دوباره بازگشته است.
پنج تلهی خاموش در وردپرس
این پروژه پنج شکست فنی بحرانی را افشا کرد که بهصورت «خاموش» رخ میدهند؛ یعنی هیچ خطایی در کنسول مرورگر چاپ نمیشود اما ویژگی بهطور کامل از کار میافتد.
تله ۱: حذف علامت و (Ampersand). وردپرس محتوای خام بلوکها را ذخیره کرده و هنگام خروجی، آنها را از فیلترهای رندرینگ عبور میدهد. در این مسیر، توالیهای && در بلوکهای HTML سفارشی به موجودیتهای HTML (HTML Entities) تبدیل میشوند. این اتفاق باعث شکست در تجزیه (Parse) جاوااسکریپت میشود. چون ویرایشگر کد را درست نشان میدهد و ذخیرهسازی با موفقیت انجام میشود، صفحه بهسادگی طوری رفتار میکند که انگار آن ویژگی هرگز وجود نداشته است.
برای تشخیص این مشکل، توسعهدهنده محتوای خام ذخیره شده را از طریق REST API (با پارامتر ?context=edit) دریافت کرد، سپس صفحه رندر شده را گرفت و طول آنها را با هم مقایسه کرد. اگر نسخه رندر شده طولانیتر از نسخه خام باشد، این یک «نشانه بد» است که میتوان آن را با جستجوی کاراکتر & در بایتهای رندر شده تأیید کرد.
راه حل این است که جاوااسکریپت را بهگونهای بنویسید که اصلاً از && استفاده نکند:
- استفاده از
ifهای تو در تو: بهجایif (a && a.b)ازif (a) { if (a.b) { ... } }استفاده کنید. - استفاده از Fallback: مثلاً
const b = (a || {}).b;. - برای علامتهای و (Ampersand) واقعی در رشتهها، از
String.fromCharCode(38)استفاده کنید.
این قانون چنان سختگیرانه است که دکمههای اشتراکگذاری X و Reddit بهصورت URLهای تک-پارامتر ساخته شدند (همه چیز در یک پارامتر text= بستهبندی شد) تا اطمینان حاصل شود هیچ & در اسکریپت ظاهر نمیشود.
تله ۲: پایان اسکریپت. قرار دادن تگ </script> داخل یک رشته متنی (مثلاً هنگام بارگذاری تنبل playhtml)، باعث میشود مرورگر در زمان تجزیه، تصور کند اسکریپت اصلی در همانجا تمام شده است. این مشکل وقتی با سکوت تله اول ترکیب شود، عیبیابی را سخت میکند. راه حل، شکستن رشته به صورت '<' + '/script>' یا \</script> است.
تله ۳: حذف ایموجیها. ایموجیهای سطح Astral-plane هنگام ذخیره از طریق REST API در بلوکهای HTML سفارشی حذف میشوند. این اتفاق برای کاراکترهای خام، موجودیتهای HTML و حتی ایموجیهایی که توسط جاوااسکریپت در زمان اجرا اضافه شده و از ذخیرهسازی جان سالم به در بردهاند، رخ میدهد. توصیه میشود بهجای آنها از متن یا اشکال SVG داخلی استفاده کنید.
تله ۴: تضاد Transform و Position Fixed. برای خروج از ستون محتوای ۶۲۰ پیکسلی قالب، از یک دستور Full-bleed استفاده شد: .page-wrap { width: 100vw; left: 50%; transform: translateX(-50%); }.
اما این transform باعث میشود Wrapper تبدیل به یک Containing Block شود. در نتیجه، هر عنصری در زیرمجموعهی آن که دارای position: fixed باشد، بهجای Viewport، نسبت به آن Wrapper ثابت میشود. این یعنی لایههای شناور (Overlays)، توستها یا بومهای محیطی (Ambient Canvases) نمیتوانند داخل محتوای صفحهای که این استایل را دارد کار کنند. راه حل، قرار دادن عناصر Fixed در بخش فوتر قالب است، جایی که در زنجیره اجداد آن هیچ Transform وجود ندارد.
تله ۵: تداخل IDهای SVG. کپی کردن یک SVG داخلی که دارای ID داخلی است (مثلاً <linearGradient id="pg">) چندین بار در یک صفحه، باعث ایجاد IDهای تکراری میشود. هر ارجاع url(#pg) فقط به اولین تعریف اشاره میکند و ۱۱ گرادینت دیگر بهطور خاموش از کار میافتند. در اتاق بازی، این موضوع منجر به ۱۲ تعریف اما ۱۱۵ ارجاع شد. راه حل، پیشوند گذاشتن برای هر ID در هر بلوک SVG و تأیید جفت شدن درست تعریفها و ارجاعات است.
رشتهی مشترک
هر یک از این شکستها خاموش هستند. هیچ خطا یا هشدار وجود ندارد و ذخیرهسازی موفق است، اما ویژگی وجود ندارد. انضباط لازم برای شکار این تلهها، «تأییدیه» (Verification) است: دریافت بایتهای ارائه شده پس از هر نوشتن و مقایسه آنها با آنچه قصد ارسالش را داشتید. این شامل مقایسه رندر-در-مقابل-خام، اسکن موجودیتها، شمارش عناصر و تستهای بازگذاری مجدد برای ویژگیهای Stateful است.
برای کاربر نهایی، این اضافات یک تکگویی (Monologue) را به یک گفتگو (Dialogue) تبدیل میکند. حضور یک مهر در ساعت ۲ صبح یا یک آهنربای جابهجا شده، مدرکی ملموس از حضور انسانهای دیگر در یک فضای دیجیتال است. تنها با یک تگ <script> و مقداری کد اصلاح شده برای دور زدن انکودینگ، سایت احساس «مسکون بودن» میکند.
اگر میخواهید هر یک از اینها را بهصورت زنده بررسی کنید: در اتاق بازی، آرکید یا هر پستی در dhseadev.online، به پایین صفحه بروید و یک مهر بزنید. من بالا رفتن شمارنده را خواهم دید، و این دقیقاً همان هدف اصلی است.
گام بعدی شما
- اگر از بلوکهای Custom HTML در وردپرس استفاده میکنید، کد خود را با REST API مقایسه کنید تا مطمئن شوید علامتهای
&&حذف نشدهاند. - برای ایجاد المانهای Fixed در صفحاتی که از Transform استفاده میکنند، آنها را به خارج از Container اصلی منتقل کنید.
- در صورت استفاده از SVGهای تکراری، حتماً IDهای داخلی را منحصربهفرد کنید.
اما داستان سختافزاری این تعاملات زنده و هزینهی سرورهای PartyKit حتی جالبتر است — به تحلیل ما دربارهی زیرساختهای Real-time مراجعه کنید.




گفتگو