Foodops
All docs

Kitchen · Updated 2026-08-23 · v3c79551

Kitchen Display System (KDS)

Audio narration for this article is generated with the weekly docs refresh.

Warning: Surface retired at the July-10 launch — see KDS App (kds.foodops.io) The in-counter-app KDS at /kitchen/index (the flame sidebar icon, the walkthrough/screenshots below) no longer exists — the counter app's router has no /kitchen route, and the legacy haafai.app.foodops Kitchen views are fully retired. The live kitchen screen is now the dedicated KDS App at kds.foodops.io (login → outlet picker → display board, five tabs Open/Preparing/Ready/Completed/Cancelled). This entry is retained for the kitchen-setup concepts that survive unchanged — configuring kitchens per outlet, category routing, kitchen printers, and plan limits (all still backed by hubits-api-sales). Treat the "How to access" and "Walkthrough" sections below as historical legacy UI, not the current surface.

Plain-English summary

The Kitchen Display System is the screen in the kitchen that shows incoming orders in real time. When a cashier creates an order at the counter, it appears here as a card so kitchen staff know what to cook. Staff can mark items as in-progress or ready, and the card moves between Open, Completed, and Cancelled tabs accordingly.

When you'd use this

Any time a restaurant has a kitchen area separate from the cashier. Replaces paper tickets — orders arrive instantly on a screen, and front-of-house staff can see at a glance when a dish is ready to serve.

How to access

  • URL: /kitchen/index
  • Sidebar: the flame icon (11th icon from the top in the Foodops sidebar)
  • Opens into: today's kitchen view, filtered to "All Kitchens" by default

KDS empty state — no orders yet, showing the All Day sidebar, Open/Completed/Cancelled tabs, and the right-side toolbar

Prerequisites

KDS only displays orders when all of these are true:

  1. The outlet has at least one kitchen configured in Settings → Outlets → Kitchens. The number allowed per outlet is plan-limited.
  2. The kitchen has at least one category routed to it. Routing happens in the kitchen configuration (e.g., "Food → Hot Kitchen", "Drinks → Bar").
  3. An order is created with items whose categories are routed to a configured kitchen. Items not routed never appear on KDS.
  4. The order is in Open, Preparing, or Ready status — Closed/Cancelled orders don't appear in the active view.

If no kitchen is configured, KDS shows "No orders found" permanently even when orders exist at the POS — this is the most common setup confusion (see Edge cases).

Walkthrough — using the KDS during service

1. Open the KDS

Click the flame sidebar icon, or navigate to /kitchen/index. The screen loads in the active kitchen view, defaulting to today's date and "All Kitchens".

2. The layout

Area What it shows
Top-left title "Kitchen" header, with a (?) help link to the in-app docs
Top-center toolbar Refresh icon, audio-alerts toggle, date picker, kitchen filter dropdown
Status tabs Open (current count) · Completed · Cancelled
Left sidebar Time groupings (e.g., "ALL DAY"), collapsible. Shows active items summary
Main area Order cards, or "No orders found"
Right toolbar Sort, Alerts, Settings (gear)

3. Filter by kitchen

If multiple kitchens exist (Bar, Hot Kitchen, Grill), use the top-right kitchen dropdown to show only orders routed to this station. "All Kitchens" shows everything.

4. Work the order cards

Each order card shows:

  • Order numberSO-NNNN/YYYY
  • Order type icon — Dine-in, Takeout, or Delivery
  • Table number — for dine-in orders only
  • Customer name — when known (Walk-In Customer otherwise)
  • Items with quantities and modifiers — one line per item
  • Time elapsed — running timer since order creation
  • Status — New → In Progress → Ready

Tap items on the card to advance their status. When all items on a card are marked Ready, the card moves to the Completed tab and is removed from the active display.

5. Sort and view options

Sort menu — Date (Newest) or Date (Oldest)

  • Sort (right toolbar): Date (Newest) or Date (Oldest)
  • Settings (gear icon): toggle Full color cards — when on, cards render with high-contrast backgrounds for visibility across the kitchen

Kitchen Settings panel — Full color cards toggle

  • Alerts (triangle icon): shows items that have exceeded expected prep time
  • Audio alerts (speaker icon top bar): toggles sound notification on new orders

What changed since 2026-05 (NEW 2026-06-03)

Three structural KDS changes since the 2026-05 watermark — verify on a live re-walk:

  • KDS is now a separate sub-application served at kds.foodops.io — the screen no longer lives at /kitchen/index inside the main counter app. It has its own entry point (src/kds/KdsApp.tsx, src/kds/main.tsx), an outlet-picker page (KdsOutletPickerPage) you land on first, and the display page (KdsDisplayPage). The main counter App.tsx has no /kitchen route anymore [code: foodops.counter.app/src/App.tsx (no /kitchen route), src/kds/KdsApp.tsx @ e002374]. The "How to access" section below (/kitchen/index, flame sidebar icon) is stale<!-- UI-TODO re-walk the kds.foodops.io entry + outlet picker flow and rewrite "How to access" -->.
  • Status tabs are now Open / Preparing / Ready / Completed / Cancelled (was Open / Completed / Cancelled) — KdsDisplayPage defines a 5-tab StatusTab set: the old "Completed" was retained, and Preparing + Ready were added between Open and Completed (not a replacement) [code: foodops.counter.app/src/kds/pages/KdsDisplayPage.tsx:36,42-48 @ main — type StatusTab = 'Open' | 'Preparing' | 'Ready' | 'Completed' | 'Cancelled', and STATUS_TABS renders all five]. The "All Day" aggregate panel and an Expo-view toggle remain. <!-- UI-TODO confirm tab labels + behaviour on live KDS -->
  • Ticket actions: "Ready All", not a Bump button — there is no "Bump" button on the kitchen ticket in the current code (and no bump/recall-chip mechanic). A ticket that is not yet done renders an action reading "Ready All" (advance every item on the card to Ready) or "Start Preparing"; once the order is closed or every item is served, that action is replaced by a static "Completed" label, and the summary line shows "Completed" / "In Progress" [code: foodops.counter.app/src/components/kitchen/KitchenTicket.tsx:804,819,845 @ main — orderStatus === 'closed' || allItemsServed ? 'Completed' : 'Ready All']. (Corrected 2026-07-28: the earlier "Bump button + recall chips" description — attributed to 5d1d387 — is not present in prod code.)

On the "feedback" feature (5d1d387): this commit's headline feature is an app-wide feedback button + screenshot-capture modal, but it is mounted in the main counter Layout ({showSidebar && <FeedbackButton />}) — the KDS sub-app does not render Layout, so the feedback button does not appear on the KDS. The only KDS-facing part of 5d1d387 is the Bump button above. [code: foodops.counter.app/src/components/layout/Layout.tsx:59, src/kds/KdsApp.tsx @ e002374]

What changed since 2026-04-15 (NEW 2026-05)

The KDS-relevant components in foodops.counter.app (src/components/kitchen/**, src/pages/KitchenPage.tsx) saw substantive activity:

  • Age-based badges on kitchen tickets (NEW 2026-04-26)KitchenTicket and OrderCard now show "new" / "late" badges based on time-elapsed since order creation (af769d0). At a glance kitchen staff can spot orders nearing their expected prep window.
  • Allergies and notes on kitchen tickets (NEW 2026-04-25) — order items with allergy flags or special notes now display them prominently on the ticket and in KOTPrintLayout (b090e52). Reduces dependence on cashier-shouted notes.
  • "Table" prefix for clarity (NEW 2026-04-26) — table names on tickets and POSPage are now prefixed "Table N" for clearer kitchen-to-floor coordination (da7df48).
  • ISO date parsing fixKitchenTicket handles ISO date strings without timezone suffix more robustly (b01b822).
  • Modifier display improvements (NEW 2026-04-25) — modifier quantities and richer display of modifier hierarchies on KDS cards (464f718, 17a4035).
  • Payment method display order (NEW 2026-04-28)displayOrder prop and consistent sorting across kitchen + payment components (3e9a9ea).
  • Expo view toggle hidden temporarily (2026-04-24) — KitchenPage's "Expo view" toggle was hidden pending UX review (1dcbcb4).
  • Background-color refresh for visibility — KDS color tokens updated for cross-component consistency (3a45c96).

Legacy monolith Kitchen views (Haafai.Restaurant.App/Views/Kitchen/*) have been dormant since the watermark — all active KDS development is in foodops.counter.app per the architecture split.

Verified 2026-05-04 (Playwright) — The KDS page loads at /kitchen with the layout described above. Empty state confirmed: Open (0) / Completed (0) / Cancelled (0) tabs; "All Day" sidebar section shows "No active items"; the right toolbar shows sort, alerts, and settings gear.

KDS empty state — Open 0 / Completed 0 / Cancelled 0 tabs, All Day section with No active items, right toolbar with sort/alerts/settings

Note: Age badges, allergy display, and "Table" prefix (commits af769d0, b090e52, da7df48) could not be verified in the 2026-05-04 playwright pass — the E2E test org has no kitchen configured, so no orders appear on KDS. These features are confirmed at the code level only. Re-verify once a test outlet with kitchen routing and active orders is available.

Key concepts

  • Kitchen — a named station within an outlet (e.g., "Hot Kitchen", "Bar", "Grill"). Configured per outlet in Settings. Each kitchen can have its own printer for automatic order-ticket printing.
  • Category routing — the mapping between product categories and kitchens. Drinks route to the Bar, mains route to Hot Kitchen. Without routing, KDS cannot decide which kitchen should see which item.
  • SignalR — the real-time transport Foodops uses to push new orders onto KDS screens without a page reload. This is why orders "just appear" instantly when cashiers hit Save & Accept.
  • Bump — kitchen-industry jargon for marking an order as done and clearing it off the active rail. In the current KDS, once a ticket's items are all Ready it shows an explicit Bump button; tapping it removes the ticket from the rail (a recall chip lets you pull it back briefly). Closed/served tickets show a static "Completed" label instead [code: foodops.counter.app/src/components/kitchen/KitchenTicket.tsx:799-826 @ e002374].
  • All Day — an aggregate view of every active item across all orders. Useful when a kitchen is tracking "how many shawarmas do I have in the queue right now" rather than "which order contains shawarmas".

Common questions

Q: I created an order on the POS but it's not showing up on KDS. What's wrong? A: Almost always, the outlet has no kitchen configured, or the item's category isn't routed to a kitchen. Go to Settings → Outlets → [your outlet] → Kitchens, create one, and route your categories to it. Then reload KDS.

Q: How many kitchens can I have per outlet? A: The number of kitchens per outlet is plan-limited and rises with the tier; see Pricing. Configured per outlet, not per organization.

Q: Does the KDS auto-refresh? A: Yes — new orders arrive over SignalR (WebSocket) without a page reload. The refresh icon in the top bar is only needed if the connection drops.

Q: Can I print kitchen tickets instead of (or in addition to) using KDS? A: Yes. Each kitchen can be assigned a receipt printer in Settings → Printers. When routed correctly, orders print automatically when created, and still appear on KDS. Most restaurants use both — KDS for real-time visibility, printed tickets as a physical backup.

Q: Can I filter KDS to show only dine-in orders? A: Not directly from KDS — the only filter is by kitchen. The status tabs (Open/Completed/Cancelled) don't filter by order type. For dine-in-specific views, use the POS Counter with its Dine-in tab, or the Tables/floor-plan view.

Q: What happens if I mark an item ready by mistake? A: Tap it again to revert to In Progress. The card won't move to Completed until every item is Ready, so a single misclick is easily undone.

Q: Does KDS work on a tablet or only a desktop browser? A: Designed tablet-first. Works in any modern browser on any device — most restaurants mount a cheap Android tablet in the kitchen running Foodops in full-screen mode.

Q: Can two kitchens see the same order? A: Yes — if an order has items routed to multiple kitchens (e.g., a burger + a cocktail), each kitchen sees the card but only the items routed to them. When both kitchens mark their items ready, the full order shows Ready front-of-house.

Edge cases and known issues

"No orders found" despite orders being on the POS

  • Symptom: Cashier creates an order at the POS counter, but KDS shows empty forever
  • Cause: No kitchen configured for the outlet, or no category routing set up
  • Workaround: Go to Settings → Outlets → [outlet] → Kitchens. Add at least one kitchen. For each, assign product categories. Reload KDS.

KDS shows yesterday's date after joining an old session

  • Symptom: The date picker at the top of KDS stays on the session-start date, so today's orders don't appear
  • Cause: The date picker defaults to the opening date of the currently-joined sales session, not today
  • Workaround: Click the date dropdown and pick today (or the range you care about). Or close the old session from Sessions → Details first.

Kitchen limit surprises

  • Symptom: You try to add a third kitchen and get a plan-upgrade prompt
  • Cause: the plan's kitchen-per-outlet limit has been reached
  • Workaround: Combine concepts (e.g., one kitchen for all food, one for drinks), or move to a higher plan

Plan limits

Plan tiers and their limits are not listed here — they are DB-backed and change with the lineup. See Pricing for the live catalogue.

API and integration notes

  • Real-time transport: SignalR hub (WebSocket with long-polling fallback)
  • Event types pushed to KDS clients (new stack, hubits-api-sales org-scoped hub): KitchenOrderAdded (on KOT creation), KitchenStatusUpdated (mark-ready back-channel), OrderCancelled [code: hubits-api-sales/Hubits.Sales.Api/Controllers/OrdersController.cs KitchenOrderAdded; OrderKitchenController.cs:181/320/451 KitchenStatusUpdated @ main]
  • Each connected KDS client subscribes to the outlet's hub group; server pushes events only to kitchens whose category filters match the order items
  • Audio alert toggle state and "Full color cards" preference are persisted client-side (per browser) via localStorage

Related

  • POS Takeaway Order — creating orders that populate KDS

  • Settings Overview — where kitchens and printers are configured

  • Navigation Module Map/kitchen/index location in the sidebar

  • User Stories: POS — US-POS-012 (view orders on KDS), US-POS-013 (mark item ready)

  • 2026-08-14 — upstream diff (hubits-api-sales ed3c7d6..04ad21d, commit 'fix(kitchen): stop shared-ingredient recipes corrupting the mark-preparing save'). The recipe/BOM stock-deduction call on the kitchen mark-preparing/mark-ready actions (documented in the 2026-07-04 verification entry) has been decoupled from the kitchen status-change save to fix a reliability bug: ProcessBomStockDeduction previously ran inline, inside the same tracked DbContext as the pending Preparing/Ready flag updates, before a single SaveChangesAsync — so a shared-ingredient recipe (which the API's NoTracking default materialises as duplicate entity instances, making the deduction's second Update() throw) could corrupt or abort that same SaveChangesAsync, silently losing the kitchen status update it was bundled with. In RestaurantKitchenController.MarkPreparing this was worse: a deduction exception hit an early return Success:false, "BOM stock deduction failed. Please try again." BEFORE the order's status was ever saved, so a broken recipe could outright block a kitchen ticket from being marked Preparing. The fix (OrderKitchenController.MarkMultiplePreparing, RestaurantKitchenController.MarkPreparing/MarkReady) now always saves the kitchen status flip FIRST, then runs the BOM deduction afterward via a new BomDeductionRunner.DeductIsolatedAsync helper that clears the DbContext's identity map per order — so a deduction failure can no longer block or corrupt the documented 'advance item status' KDS action, and any undeducted line is still picked up idempotently at settle (per the PaymentProcessingService fallback already documented 2026-07-04/07-29). Net effect for this entry: no existing text is contradicted (the entry never claimed status-change/deduction atomicity), but worth a line near the existing BOM-deduction mentions in Key concepts/API notes: marking an item preparing or ready on KDS now reliably updates the kitchen status even if the outlet's recipe/BOM stock deduction fails (e.g. a shared-ingredient recipe), instead of that failure risking or blocking the status update. Not yet verified live; code-level only. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-10 — upstream diff (foodops.counter.app 8c411fc..7c23e6d, commit 'fix(kot): print the manual KOT in the org timezone, not the terminal's'). KOTPrintLayout.tsx's formatDate/formatDateTime previously read the raw orderDate string (no 'Z' suffix, a naked UTC instant) via plain new Date(dateString), which the browser parses as terminal-local — so a kitchen ticket printed from a terminal whose OS clock/locale differs from the outlet's configured timezone showed the wrong printed date/time (per the added code comment, the KOT print-data endpoint, like receipt-print-data, skips the org-timezone conversion the broker endpoints run). The fix now parses via parseServerDate (forces UTC reading) and renders with getOrgTimeZone() applied as an explicit timeZone option, with a localStorage-persisted org timezone that survives a cold-loaded print tab and falls back to the terminal clock only if unset. formatTime also gained an isNaN guard so a malformed time string now returns the raw string instead of rendering 'Invalid Date'. Deliberately NOT touched: EstimatedPickupTime (a zoneless TimeSpan wall-clock string) still prints verbatim, unconverted — converting it would shift a value that's already correct. A related defensive fix in the same file: the organisation-logo fallback (kotData.applicationOrganisation.logoImage?.url) is now optional-chained (applicationOrganisation?.logoImage?.url) — the kitchen-tickets print-data endpoint omits applicationOrganisation on the wire despite its non-nullable type, so the print tab could previously white-screen for any outlet whose register/outlet carried no logo. This entry doesn't currently document KOT print date/time handling anywhere (the 'Can I print kitchen tickets' FAQ and API/integration notes are silent on timezone), so nothing is contradicted — but it's worth a line near that FAQ or in Key concepts: printed kitchen tickets now render the order date/time in the outlet's configured timezone rather than the printing terminal's local clock, fixing a prior mismatch for outlets whose terminals aren't set to local time (e.g. Malé/Indian Ocean zones). Not yet verified live; code-level only, backed by new unit tests (KOTPrintLayout.timezone.test.tsx) covering org-timezone rendering, a midnight-rollover case, pickup-time verbatim printing, and the no-org-timezone-cached fallback. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-08 — upstream diff (hubits-api-sales bb69312..cdb3b32; same commit subjects as the 2026-08-01 entry but a later end-commit exposing additional hunks not surfaced there). Two additions worth documenting; neither contradicts existing entry text. (1) Open/Preparing tab overlap is now explicit by design: KitchenController.cs introduces shared OpenBucket/PreparingBucket/CompletedBucket/CancelledBucket expressions used by both the ticket-list and counts endpoints (replacing logic that was 'hand-duplicated across the two endpoints before, which is how the buckets drifted apart'). Per the added code comment, a ticket with at least one untouched item stays in the Open bucket even after another item on the same ticket has started preparing, so a partially-started ticket now shows on BOTH the Open and Preparing tabs simultaneously — tab membership is not a mutually-exclusive partition. The entry doesn't currently claim exclusivity for the live kds.foodops.io tabs (only the flagged-historical legacy walkthrough implies cards move between tabs), so this isn't a contradiction, but it's worth a line in Key concepts. (2) New auto-close-on-settlement path: OrderKitchenController.MarkMultipleServed now calls a new TryCloseSettledOrderAsync per order after items are marked served — if the order is fully settled (Outstanding <= 0.01 or a credit sale) and has just reached a terminal service status, the order auto-closes and, for dine-in orders, its table is set back to Vacant. This is new, user-facing behavior triggered directly by the KDS 'mark served' action and isn't covered anywhere in this entry — relevant to the FAQ's pointer to the 'Tables/floor-plan view', since marking the last item served on KDS can now free a dine-in table automatically instead of requiring a separate close-order step at the counter. Neither change is verified live; code-level only. The multi-kitchen join-filter fix and the SignalR routing-envelope (IOrderEventPublisher) changes also visible in this diff were already captured in the 2026-08-01 verification entry above and are not repeated here. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-01 — upstream diff (hubits-api-sales bb69312..0d94f42, commits 'chore(tenancy): pin haafai.domain 1.0.1027', 'perf(cache): explicit invalidation seam for ExecuteUpdate writes; enable order micro-cache', 'perf(signalr): give order broadcasts a routing envelope so terminals stop refetching', 'fix(dependencies): update haafai.domain package version to 1.0.1029', 'feat(customers): add org-wide paged statements endpoint'). Two changes intersect this entry's cited surface. (1) Bug fix relevant to the documented 'Can two kitchens see the same order?' FAQ: KitchenController.GetKitchenTickets/GetKitchenTicketCounts and RestaurantKitchenController.GetKitchenOrders/GetOrdersCounts previously filtered a kitchen-ticket query on only the ticket's singular RestaurantKitchenId column; per the added code comments, a ticket routed to multiple kitchens carries only its FIRST kitchen in that column, so a ticket's second/third kitchen was silently excluded from that kitchen's KDS list and count. The diff adds an Any(kk => kk.RestaurantKitchenId == ...) check against the KitchenOrderTicketKitchens many-to-many join in all four query methods, closing that gap. Net effect: this is a latent-bug fix that makes the entry's existing FAQ claim ('each kitchen sees the card but only the items routed to them... both kitchens mark their items ready') actually hold for tickets spanning 3+ kitchens or wherever the multi-kitchen join previously diverged from the singular column — not a contradiction, since the documented behavior was the intended/target behavior all along. (2) OrderKitchenController (mark-served/mark-preparing/mark-ready handlers) and RestaurantKitchenController (MarkDone/MarkPreparing/MarkReady) now route their existing KitchenStatusUpdated/OrderReady/KitchenOrderUpdated/OrderUpdated SignalR broadcasts through a new IOrderEventPublisher envelope carrying salesOrderId, sessionId, and scope fields, resolving each order's register-session in one batched query. This is purely a server-side fan-out/perf optimization (terminals on other registers can now ignore an event instead of reloading) — event names, triggering actions, and payload's existing fields are unchanged, so nothing here contradicts the documented 'API and integration notes' event list. Neither change is verified live; code-level only. No entry text requires correction; worth a short Key-concepts/API-notes line noting that multi-kitchen ticket routing (a ticket assigned to more than one kitchen) is now correctly join-matched end-to-end rather than only via the ticket's primary kitchen. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-29 — upstream diff (hubits-api-sales 9f561de..a26fdc7, commits 'feat(orders): implement duplicate-create guard for CounterLocalReference', 'feat(caching): implement inventory stock invalidation publisher and integrate with payment processing', 'feat(tests): ...'). The BOM/recipe stock deduction that already fires on kitchen mark-preparing/mark-ready (documented in the 2026-07-04 verification entry) now also triggers cache/UI invalidation: both OrderKitchenController.MarkMultiplePreparing and RestaurantKitchenController.MarkPreparing/MarkReady call a new PublishBomStockDeductionInvalidationAsync immediately after the BOM deduction is saved, which (a) invalidates the org's cached product list (ICacheInvalidator.InvalidateAllProductsForOrgAsync), (b) pushes a new MenuItemsUpdated SignalR event to the organisation hub group, and (c) publishes to InventoryStockInvalidationPublisher. Per the added code comment, this closes a staleness gap: BOM deduction mutates ProductVariant.CurrentStockLevel directly via EF, out of band from paths that already bust the product cache, so the admin app's items/stock list previously could show stale stock levels until cache TTL expiry after a kitchen-side deduction. Net effect for this entry: no contradiction to documented KDS behavior (ticket tabs, Ready All/Start Preparing action, KitchenOrderAdded/KitchenStatusUpdated/OrderCancelled events are all untouched by this diff) — MenuItemsUpdated is an org-wide menu/stock cache-bust event, not a KDS ticket-display event, so it doesn't belong in the 'Event types pushed to KDS clients' list. Worth a line in Key concepts/API notes near the existing BOM-deduction mention: marking an item preparing/ready on KDS now also promptly refreshes stock levels shown elsewhere (e.g. admin product list) instead of waiting on cache TTL. Not yet verified live; code-level only. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-18 — upstream diff (hubits-api-sales 074c027..4c95164, commit 'fix(kitchen): prioritize live line-item notes over KOT snapshot notes'). KitchenController.GetKitchenTickets now sources each ticket item's Note from the live SalesOrderLineItem.Note when present, falling back to the KOT snapshot Note only if the line item has none — previously it always used the KOT-time snapshot. Practical effect: if a cashier or ordering-app customer edits/adds a note on a line item after the KOT was already generated and sent to the kitchen, that updated note now reaches the KDS/kitchen ticket instead of showing the stale text captured at KOT creation. This refines (does not contradict) the existing 'Allergies and notes on kitchen tickets' behavior documented under 'What changed since 2026-04-15' — worth a line in Key concepts/API notes that ticket notes are live rather than frozen at KOT creation. Not yet verified live; code-level only. Separately, this diff also (a) rewrote GetKitchenTickets/GetKitchenTicketCounts status-bucket membership to derive Open/Preparing/Ready/Completed from item state rather than solely the stored IsCompleted/HasPreparingStarted flags (self-healing against stale flags — no visible change to documented tab semantics), (b) made MarkMultiplePreparing idempotent instead of a toggle (repeat calls no longer flip an already-preparing item back to New), (c) fixed a race condition in MarkMultipleReady's ticket-completion check via committed reads, and (d) closed an IDOR gap by org-scoping MarkServed/MarkMultipleServed lookups to the caller's organisation. None of these contradict documented KDS behavior; (b)-(d) are internal correctness/security hardening with no new user-facing surface worth a doc edit. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-04 — upstream diff (hubits-api-sales 30a126a..a4c6ced, commit 'feat: deduct recipe/BOM ingredient stock on KDS mark-preparing and at payment (idempotent)'). OrderKitchenController.MarkMultiplePreparing now calls salesOrder.ProcessBomStockDeduction(...) (wrapped in try/catch, logged on failure) the moment an order's status is set to Preparing — i.e. when kitchen staff advance a ticket to 'Preparing' on the KDS, the system now automatically deducts recipe/BOM ingredient stock, in addition to the existing deduction at payment. The deduction is idempotent (guarded by an IsBomStockDeducted flag per the commit message), so re-marking an order preparing does not double-deduct. This is a new inventory-side effect of the documented 'advance item status' KDS action and is not yet reflected in the entry's Key concepts / API integration notes — worth a line noting that marking items preparing on KDS now triggers ingredient stock deduction for outlets using recipe/BOM tracking. Not verified against a live outlet with BOM-tracked items; code-level only. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-06-29 — codebase (July-10 launch re-verify; foodops.counter.app@32669a9 main, hubits-api-sales@19190b9 main). Confirmed the in-counter-app /kitchen surface is fully gone (no /kitchen route in App.tsx; src/pages/KitchenPage.tsx removed) and the legacy haafai.app.foodops Kitchen views are retired at launch — added a top-of-page deprecation banner pointing to kitchen-kds.md (the live kds.foodops.io app, five tabs Open/Preparing/Ready/Completed/Cancelled per src/kds/pages/KdsDisplayPage.tsx:35-43). Dropped haafai.app.foodops + the removed KitchenPage.tsx from frontmatter; re-pointed the SignalR event-name note to the hubits-api-sales hub (KitchenOrderAdded/KitchenStatusUpdated/OrderCancelled). The surviving setup concepts (kitchens, category routing, printers, plan limits) are unchanged. The "How to access" + "Walkthrough" + screenshots are retained as historical legacy UI (flagged); a live re-walk of the new app belongs in kitchen-kds.md. Frontmatter SHAs + last_verified bumped.

Note: Code + Playwright verified 2026-05-04 · codebase + live E2E (partial) Verified_by: playwright · Last verified: 2026-05-04 Glob curated to src/pages/KitchenPage.tsx;src/components/kitchen/** (counter.app) and Haafai.Restaurant.App/Views/Kitchen/**;Haafai.Restaurant.App/Controllers/KitchenController.cs (legacy monolith). Playwright pass (2026-05-04) confirmed: KDS page loads at /kitchen; Open/Completed/Cancelled tab structure correct; All Day sidebar section present; right toolbar sort/alerts/settings gear visible. Screenshot 149 captured. Age badges (commit af769d0), allergy/notes display (commit b090e52), "Table" prefix (commit da7df48) remain code-only — E2E test org has no kitchen routing configured. Status remains needs-review until active-order state is verifiable. Legacy monolith Kitchen views dormant since watermark.

2026-06-03 — content re-verification against foodops.counter.app@e002374 and hubits-api-sales@5274891. Three real structural changes captured in the new "What changed (2026-06-03)" block: (1) KDS is now a separate sub-app at kds.foodops.io with its own src/kds/KdsApp.tsx entry + outlet-picker — the documented /kitchen/index route no longer exists in the counter App.tsx; "How to access" flagged stale with UI-TODO. (2) Status tabs changed to Open/Preparing/Ready/Cancelled (was Open/Completed/Cancelled). (3) Bump button on Ready tickets (5d1d387, 2026-05-11). IMPORTANT correction to the task's framing: the 5d1d387 "feedback" feature is an app-wide feedback button mounted in the counter Layout and does NOT appear on the KDS (which doesn't render Layout); only the Bump button is KDS-facing. Full walkthrough/screenshots NOT rewritten — they require a live re-walk against the new kds.foodops.io surface (auth refresh pending). Status remains needs-review.

Additional verification events are appended here by the weekly KB sync and by manual re-verification passes. See _meta/failure-log.md for the global failure index.

Go beyond POS.
Choose the total solution.

Focus on creating the best experience for your guests while we handle the rest.