Skip to content

06 — Kitchen (KDS), Waiter & Rider Apps

Route roots: /[locale]/tenants/[tenant]/kitchen, /waiter, /rider · Access: staff-only, mobile/tablet-first · Status: ✅ UI fully built, real-time simulated via localStorage

🟢 বাংলা ভূমিকা: এই ডকুমেন্টে কিচেন ডিসপ্লে সিস্টেম (KDS), ওয়েটার অ্যাপ, এবং রাইডার অ্যাপ — এবং এই তিনটির মধ্যে অর্ডার স্ট্যাটাস কীভাবে রিয়েল-টাইম সিঙ্ক থাকে তা বর্ণনা করা হয়েছে।


1. The Shared Real-Time Backbone (current simulation)

Hook: use-live-orders.ts

All three staff surfaces — KDS, Waiter, Rider — plus the Orders Board in the owner dashboard (04 §4) read and write the same tenant-scoped order list:

function useLiveOrders(tenantSlug: string): {
  orders: Order[];
  addOrder(order: Order): void;
  advanceStatus(orderId: string, status: OrderStatus, label: string, fallbackOrder?: Order): void;
};
  • Storage key: ros-live-orders:{tenantSlug} in localStorage, via the shared useLocalStorage hook.
  • Cross-surface sync mechanism: useLocalStorage is built on useSyncExternalStore and listens to the browser's native storage event, so a status change written from one browser tab (e.g. KDS marking an order "ready") is picked up by every other open tab/surface (Waiter, Dashboard) reading the same key — within the same browser only. This is a faithful simulation of real-time push, not real cross-device real-time (two different physical devices — e.g. a kitchen tablet and a waiter's phone — do not currently see each other's updates, since localStorage doesn't cross devices).
  • advanceStatus appends an immutable OrderTimelineEvent (status, label, timestamp) rather than overwriting history, so every surface can render a full audit trail per order.
  • Static seed orders (from getOrdersForTenant mock data) are merged at render time alongside whatever lives in this hook's localStorage list — fallbackOrder lets advanceStatus "adopt" a static seed order into the live list the first time its status changes, so history isn't lost.

Target production replacement: Supabase Realtime (Postgres logical replication → WebSocket channel per tenant, e.g. orders:{tenantId}), which is genuinely cross-device. The hook's external shape (orders, addOrder, advanceStatus) is designed to stay identical — only the internals swap from localStorage + storage event to a Supabase channel subscription + postgres_changes listener, so zero component code changes are needed in KDS/Waiter/Rider/Dashboard when the backend lands.

🟢 বাংলা সারাংশ: KDS, ওয়েটার, রাইডার, এবং ড্যাশবোর্ড অর্ডার বোর্ড — এই সবগুলো একই useLiveOrders হুক ব্যবহার করে, যা localStorage-এ ডেটা রাখে এবং ব্রাউজারের storage ইভেন্ট শুনে এক ট্যাব থেকে আরেক ট্যাবে সিঙ্ক করে। এটা একই ব্রাউজারের মধ্যে কাজ করে, কিন্তু দুটি আলাদা ডিভাইসের মধ্যে (যেমন কিচেনের ট্যাবলেট আর ওয়েটারের ফোন) সিঙ্ক করে না — এর জন্য ভবিষ্যতে Supabase Realtime (WebSocket) ব্যবহার করা হবে, তবে হুকের বাইরের ইন্টারফেস একই থাকবে বলে কোনো কম্পোনেন্ট পরিবর্তন করতে হবে না।


2. Kitchen Display System — /kitchen

Components: kds-board.tsx, pickup-screen-board.tsx

2.1 KDS Board

Sold out, from the pass. A "Sold out" control in the KDS header opens the menu as a list the kitchen can tap: one tap takes a dish off, the badge counts what is off, and "Put everything back" clears the lot at the start of a shift. It writes ros-menu-sold-out:{tenant}, which the guest menu applies through applySoldOut() before anything else reads the menu — so the dish disappears from the next scan, and any meal package that needed it stops being offered (isPackageOrderable). This is the difference between a QR menu and a printed one, and it belongs to the people who actually know: the owner pausing a dish in the menu builder is a different statement, and a different undo.

🟢 বাংলা সারাংশ: KDS-এর হেডারে "শেষ" বোতাম — এক ট্যাপে ডিশ মেনু থেকে সরে যায়, পরের স্ক্যানে অতিথি আর দেখেন না, আর ওই ডিশ লাগে এমন প্যাকেজও বন্ধ হয়ে যায়। শিফট শুরুর সময় "সব ফিরিয়ে দিন" দিয়ে একবারে সব ফিরিয়ে আনা যায়। মালিকের "পজ" করা আর রান্নাঘরের "আজ শেষ" — দুটো আলাদা কথা, আলাদা undo।

  • Station-filtered ticket columns — orders are grouped by Order.station (grill | fry | cold | bar | dessert | curry), so a busy kitchen can run one tablet per station or one shared "all stations" view.
  • Ticket card — order number, table label (or channel badge for takeaway/delivery), elapsed timer (color escalates from neutral → amber → red as it ages past configurable thresholds — the primary KDS UX signal for prioritization), full line-item list with variant/addon/notes, and large touch-friendly status-advance buttons (Pending → Preparing → Ready).
  • Bump/recall — completing a ticket "bumps" it off the active board; a recall action restores it if bumped by mistake.
  • Sound — a two-note chime for a new ticket (and, on /waiter, a new table call), synthesised with Web Audio so there is nothing to download (src/lib/chime.ts). Off until someone taps the speaker in the header: browsers refuse sound from a page nobody has touched, and a laptop on the same dashboard may not want it. The choice is per device (ros-sound:kitchen, ros-sound:waiter); after a reload the first tap anywhere re-arms it. The browser notification is still there, but it only helps when the tab is out of focus — the chime is for the cook with both hands full who is not looking at the screen.
  • Motion — new tickets slide/fade in, status changes trigger a colored flash + haptic-style scale pulse (Framer Motion), so kitchen staff notice new work without needing to stare at the screen.

2.2 Pickup Screen (pickup-screen-board.tsx)

A customer/front-of-house-facing companion screen (think: a TV mounted above a takeaway counter) listing orders in ready status by order number/pickup code only (no sensitive details), so customers know when to approach the counter — mirrors the physical "now serving" board pattern seen in food courts.

🟢 বাংলা সারাংশ: KDS বোর্ডে স্টেশন অনুযায়ী (গ্রিল/ফ্রাই/কোল্ড/বার/ডেজার্ট/কারি) টিকিট কলামে ভাগ করা থাকে। প্রতিটি টিকিটে অর্ডার নম্বর, সময় (যত দেরি হবে রঙ লাল হয়ে যাবে), এবং বড় বাটনে স্ট্যাটাস পরিবর্তনের অপশন থাকে। পিকআপ স্ক্রিন একটা পাবলিক ডিসপ্লে যেখানে শুধু "রেডি" অর্ডারের নম্বর দেখানো হয় (কোনো সংবেদনশীল তথ্য ছাড়া)।


3. Waiter App — /waiter

Component: waiter-board.tsx

  • Assigned tables view — a waiter sees only orders/tables assigned to them (or all, if unassigned/manager view), grouped by table.

  • Status actions relevant to front-of-house: mark "served" once food is delivered from the kitchen pass, respond to use-table-calls.ts signals ("call waiter"/"request bill") which surface here the instant they're raised from /table, and trigger the split-bill/payment-collection flow at the table.

  • Notification badge/haptic-style pulse for new incoming table calls, so a busy waiter doesn't need to keep the screen open and staring — matches the design mandate for native-app-like touch feedback.

  • Spatial floor plan (floor-plan.tsx, toggled against the list view) — the tables drawn where they sit in the room rather than in a grid. The grid answers "which tables exist", which a waiter already knows; what they need mid-shift is which table to walk to, and a list sorted by number cannot say that. Mapping "T-07" onto a spot in the room is work the waiter otherwise does in their head while carrying plates. Both views stay, because the grid is still the better one for checking a specific table's status.

    • Positions come from RestaurantTable.floorX/floorY, percentages of the floor area so one layout survives a phone, a tablet on the pass and a desktop. They are seeded per zone into bands (main hall across the middle, rooftop above, patio and bar along the edges) rather than authored per tenant, since no real floor plan exists to copy — an owner will place their own once there is somewhere to save them.
    • A table asking for the bill pulses regardless of its seating status, because that is the one tile with a person waiting behind it. Colour still carries seating status, so the two signals do not compete.
    • tables.test.ts asserts no two tables in any tenant share a spot and none sits at the edge. A collision draws one table entirely underneath another — invisible and unclickable, so a waiter never sees that table's bill request. It would fail silently, in one zone of one tenant.

🟢 বাংলা সারাংশ: ওয়েটার অ্যাপে নিজের বরাদ্দকৃত টেবিল/অর্ডার দেখা যায়, খাবার সার্ভ করার পর "served" মার্ক করা যায়, এবং কাস্টমারের "ওয়েটার ডাকো"/"বিল চাই" সিগন্যাল সাথে সাথে নোটিফিকেশন হিসেবে দেখা যায়। এখন একটা ফ্লোর প্ল্যান ভিউও আছে — টেবিলগুলো ঘরে যেখানে আছে সেভাবেই আঁকা। গ্রিড বলে কোন কোন টেবিল আছে, যেটা ওয়েটার এমনিতেই জানেন; শিফটের মাঝে তার দরকার কোন টেবিলে হেঁটে যেতে হবে — নম্বর অনুযায়ী সাজানো তালিকা সেটা বলতে পারে না। বিল চাওয়া টেবিল স্পন্দিত হয়, কারণ ওই একটা টাইলের পেছনেই একজন মানুষ অপেক্ষা করছেন। দুটো টেবিল যেন একই জায়গায় না পড়ে (তাহলে একটা সম্পূর্ণ ঢাকা পড়ে যেত) — সেটা টেস্ট দিয়ে নিশ্চিত করা।


4. Rider App — /rider

Component: rider-board.tsx

  • Delivery queue — orders with channel: "delivery" and status out-for-delivery (or ready, pending pickup), each showing deliveryAddress, customerPhone, and a "navigate" deep-link (target: opens the device's native maps app).
  • Status progression: Ready → Picked up → Out for delivery → Delivered, each tap calling advanceStatus so the customer-facing order tracker (05 §6) and dashboard update immediately.
  • Earnings/trip summary (mock) — per-rider count of completed deliveries and estimated earnings for the shift, motivational for gig-style riders.

🟢 বাংলা সারাংশ: রাইডার অ্যাপে ডেলিভারি অর্ডারের লিস্ট থাকে (ঠিকানা, ফোন নম্বর, ম্যাপ নেভিগেশন লিংকসহ)। রাইডার প্রতিটি ধাপে (Ready → Picked up → Out for delivery → Delivered) স্ট্যাটাস আপডেট করলে তা সাথে সাথে কাস্টমার ও ড্যাশবোর্ডে দেখা যায়। শিফটের মোট ডেলিভারি ও আনুমানিক আয়ও দেখানো হয়।


5. Design & Motion Consistency Across Staff Apps

All three surfaces share the same interaction philosophy mandated for the whole product:

  • Large, thumb-friendly touch targets (staff are often wearing gloves or moving quickly).
  • Optimistic UI — a status-advance button reflects the new state immediately on tap, with the underlying advanceStatus write happening synchronously against localStorage (and, in production, an optimistic Realtime write with server reconciliation).
  • Dark-mode-friendly ticket cards for kitchen environments (often dim, high-contrast needed).
  • Consistent status-color language across KDS/Waiter/Rider/Dashboard: the same OrderStatus always renders the same accent color everywhere in the app, so staff never have to relearn color meaning per surface.

🟢 বাংলা সারাংশ: তিনটি স্টাফ অ্যাপেই বড় টাচ টার্গেট, অপ্টিমিস্টিক UI (ট্যাপ করার সাথে সাথে স্ট্যাটাস পরিবর্তন দেখানো), ডার্ক-মোড উপযোগী ডিজাইন, এবং সবজায়গায় একই স্ট্যাটাস-কালার ভাষা ব্যবহার করা হয়েছে — যাতে স্টাফরা প্রতিটি স্ক্রিনে নতুন করে রঙের মানে শিখতে না হয়।


6. Future Hardware Integration Point

KOT (Kitchen Order Ticket) thermal printing is not yet wired — Order creation will eventually trigger an ESC/POS print job to a station-specific thermal printer alongside (not instead of) the KDS ticket, for kitchens that want a physical backup. See 08-payment-and-sms-integrations.md for the planned ESC/POS command spec.

🟢 বাংলা সারাংশ: KOT (কিচেন অর্ডার টিকেট) থার্মাল প্রিন্টিং এখনো যুক্ত করা হয়নি — ভবিষ্যতে প্রতিটি অর্ডার তৈরি হলে KDS টিকিটের পাশাপাশি স্টেশন-নির্দিষ্ট থার্মাল প্রিন্টারেও একটা প্রিন্ট জব পাঠানো হবে।