00 — The Core Loop (read this before anything else)
Product: RestaurantOS — a dynamic-QR, multi-vendor restaurant platform Status: ✅ Built end to end on mock data — every file named below exists today Audience: Anyone — human or AI — about to touch this codebase for the first time
📖 কীভাবে পড়বেন: প্রতিটি সেকশনে আগে ইংরেজিতে ব্যাখ্যা, তারপর 🟢 বাংলা সারাংশ। ফাইলের পাথ ও কোড ইংরেজিতেই থাকবে।
1. What this product actually is
A guest sits at a table, scans the QR taped to it, sees that restaurant's menu, and orders — no app install, no waiter needed to start. That single journey is the product. Everything else in this repository exists to serve it.
Two words in the name carry the whole idea:
Multi-vendor. This is not one restaurant's software. One codebase and one deployment serve any number of independent restaurants ("tenants" in the code, "vendors" in conversation), each on their own subdomain or their own custom domain, each with their own menu, branding, tables, staff and orders. A vendor signs up and gets an isolated, branded instance — no fork, no redeploy.
Dynamic QR. This is the part that separates the product from a PDF menu behind a QR code, and it is the thing to protect in every design decision:
The QR sticker on the table is printed once and never changes. Everything behind it — which menu, which prices, which table, whether that table is occupied, which waiter is assigned — is resolved live, on every single scan.
A static QR menu is a document. This is a session. That difference is why the platform can raise a price at 6pm and have it apply to the next scan, retire a dish mid-service, or invalidate every previously printed QR for a table the moment the owner regenerates it — all without anyone reprinting anything.
The useful test for any new feature: does it make the scan-the-QR moment more dynamic, or is it a generic SaaS bolt-on? Ordering, allergy filtering, live order tracking and call-waiter all pass. A feature that would work equally well on a printed menu deserves a harder look.
🟢 বাংলা সারাংশ: এই প্রোডাক্টের মূল কথা একটাই — অতিথি টেবিলে বসে QR স্ক্যান করে, সেই রেস্টুরেন্টের মেনু দেখে, আর অর্ডার করে; কোনো অ্যাপ লাগে না, ওয়েটার ডাকতে হয় না। বাকি সব কোড এই যাত্রাটাকে সেবা দেওয়ার জন্য।
নামের দুটি শব্দে পুরো ধারণাটা: Multi-vendor — এটি একটি রেস্টুরেন্টের সফটওয়্যার নয়; একটিমাত্র কোডবেস অসংখ্য স্বাধীন রেস্টুরেন্ট চালায়, প্রত্যেকে নিজস্ব সাবডোমেইন বা নিজস্ব ডোমেইনে, নিজস্ব মেনু-ব্র্যান্ডিং-টেবিল-স্টাফ নিয়ে। Dynamic QR — টেবিলের QR স্টিকার একবারই ছাপা হয়, আর কখনো বদলায় না; কিন্তু তার পেছনের সবকিছু — কোন মেনু, কোন দাম, কোন টেবিল, টেবিলটি খালি কিনা, কোন ওয়েটার — প্রতিবার স্ক্যানে নতুন করে নির্ধারিত হয়।
স্ট্যাটিক QR মেনু একটি ডকুমেন্ট; এটি একটি সেশন। এই পার্থক্যের কারণেই সন্ধ্যা ৬টায় দাম বদলালে পরের স্ক্যানেই তা কার্যকর হয়, কোনো কিছু পুনরায় ছাপাতে হয় না। নতুন ফিচার যাচাইয়ের মাপকাঠি: এটি কি QR স্ক্যানের মুহূর্তটিকে আরও গতিশীল করে, নাকি এটি সাধারণ SaaS-এর বাড়তি জিনিস?
2. The loop, traced through the real code
Follow it once and most of the repository explains itself. Every path below is a file that exists today.
| # | Step | Where it happens |
|---|---|---|
| 1 | The QR is printed carrying {tenantOrigin}/table?table=<id>&token=<qrToken> | getTenantOrigin() in src/lib/utils.ts, rendered by src/components/dashboard/tables-floor.tsx |
| 2 | The scan arrives and the host name decides which vendor it belongs to — subdomain, custom domain, or local dev — then is rewritten (never redirected) so the guest keeps seeing a clean URL | src/proxy.ts |
| 3 | The table is resolved from ?table=; an unknown id falls back to Takeaway browsing rather than a 404 | src/app/[locale]/tenants/[tenant]/table/page.tsx |
| 4 | The token is checked against that table's currently active token; an old printed QR shows the expired screen instead of the menu | useTableTokens.isTokenValid → menu-experience.tsx → qr-expired-screen.tsx |
| 5 | That vendor's menu renders, with allergy filtering, search and categories | getMenuForTenant() → MenuExperience |
| 6 | Items go into a cart scoped to this vendor and this table, so two tables never merge | CartProvider, key ros-cart:{tenantSlug}:{tableId ?? "takeaway"} |
| 7 | The order is placed into the vendor's live order feed | useLiveOrders(tenantSlug).addOrder → key ros-live-orders:{tenantSlug} |
| 8 | Every staff surface sees it at once — kitchen, waiter, owner dashboard all read that same feed | kds-board.tsx, waiter-board.tsx, orders-board.tsx, all via useLiveOrders |
| 9 | The guest tracks their own order back on the menu screen | useMyOrderIds + useLiveOrders in menu-experience.tsx |
Steps 7–9 are why useLiveOrders matters more than its size suggests: it is the single junction where the customer half of the loop hands off to the staff half. One writer, many readers.
🟢 বাংলা সারাংশ: উপরের টেবিলে পুরো লুপটি বাস্তব ফাইল ধরে ধরে দেখানো — QR ছাপা (getTenantOrigin), স্ক্যান আসার পর হোস্ট দেখে ভেন্ডর নির্ধারণ (proxy.ts), টেবিল রেজলভ, টোকেন যাচাই, মেনু রেন্ডার, টেবিল-ভিত্তিক কার্ট, অর্ডার জমা, এবং সেই অর্ডার একই সঙ্গে কিচেন/ওয়েটার/ড্যাশবোর্ডে ভেসে ওঠা। useLiveOrders হলো সেই সংযোগস্থল যেখানে কাস্টমারের অর্ধেক লুপ স্টাফের অর্ধেকের হাতে যায় — একজন লেখে, অনেকে পড়ে।
3. What is real today, and what is staged
The loop above genuinely runs — you can scan, browse, order, and watch it appear on the kitchen board. But it runs on one device.
There is no backend yet, by explicit decision: every screen is built on mock data first, and the persistence layer is localStorage keyed per tenant (see src/hooks/use-local-storage.ts). So "the order reaches the kitchen" means the kitchen display open in another tab of the same browser, not a tablet across the room. src/lib/mock-data/* stands in for the database; useLiveOrders stands in for Supabase Realtime.
This is a real, accepted limitation of the current phase — not a bug to fix by inventing a fake server. Docs 07–12 describe the production architecture that replaces it. The hooks are shaped so that swap changes their internals and not their callers.
🟢 বাংলা সারাংশ: লুপটি সত্যিই চলে — স্ক্যান করে অর্ডার দিলে তা কিচেন বোর্ডে দেখা যায়। কিন্তু একই ডিভাইসে। এখনো কোনো ব্যাকএন্ড নেই, এটি ইচ্ছাকৃত সিদ্ধান্ত: আগে সব স্ক্রিন মক ডেটায় তৈরি হবে। তাই "অর্ডার কিচেনে পৌঁছায়" মানে একই ব্রাউজারের অন্য ট্যাবে খোলা কিচেন ডিসপ্লে, ঘরের ওপাশের ট্যাব নয়। এটি বর্তমান ধাপের স্বীকৃত সীমাবদ্ধতা — নকল সার্ভার বানিয়ে "ঠিক" করার জিনিস নয়। ০৭–১২ ডকে আসল আর্কিটেকচার বর্ণনা করা আছে।
4. Where to go next
- The rules you must not break while working here: root
CLAUDE.md(identical content inAGENTS.md) - Every route and folder, and how the proxy resolves a vendor: 02-folder-and-routing.md
- The customer half of the loop in depth, including the allergy engine: 05-customer-menu-and-allergies.md
- The staff half — kitchen, waiter, rider: 06-kitchen-waiter-rider-apps.md
- What the vendor configures: 04-restaurant-dashboard-spec.md
- What the platform operator does across vendors: 03-platform-admin-spec.md
- The full index, including the target backend design: readme.md
🟢 বাংলা সারাংশ: এরপর কোথায় যাবেন — কাজ করার নিয়মকানুনের জন্য রুট CLAUDE.md; রুট ও ফোল্ডার এবং প্রক্সি কীভাবে ভেন্ডর নির্ধারণ করে তার জন্য ০২; কাস্টমারের অংশের বিস্তারিত ০৫; স্টাফের অংশ ০৬; ভেন্ডর কী কনফিগার করে ০৪; প্ল্যাটফর্ম অপারেটর কী করে ০৩; এবং সম্পূর্ণ সূচি ও ভবিষ্যতের ব্যাকএন্ড ডিজাইনের জন্য readme।