Plain-English summary
Takeouts is the queue for orders placed for pickup — the customer orders at the counter (or online), waits or comes back, and collects. On the counter app the takeout queue is the POS order screen in takeout mode: the same session POS filtered to takeout orders, with tabs Open → Preparing → Ready → Picked → Cancelled. A takeout order's "done" state is Picked (picked up), and the system flips an order to Picked automatically once the kitchen marks all of its items served. Distinct from Dine-In (seated, terminal state Served) and Deliveries (goes out, terminal state Delivered).
When you'd use this
- Tracking takeaway orders through prep → ready → picked up
- Calling out order numbers when food is ready for collection
- Reviewing takeout vs dine-in mix
- Creating a new takeout order during a counter rush
How to access
- Sidebar: "Takeouts" — with an open session it navigates to
/sessions/{sessionId}/pos?mode=takeout; with no session it routes to/counter?intent=takeoutso you open a register first. [code: foodops.counter.app/src/components/layout/Sidebar.tsx:321-337] - Direct URL:
/takeout(singular) exists as a route, but the page behind it is a placeholder — a "Takeout Orders" heading and a non-functional "New Takeout Order" button. The working queue is POS takeout mode. [code: src/App.tsx:184-193; src/pages/TakeoutPage.tsx] - Takeout mode is restaurant-brand-only (the salesmade retail build filters Takeouts out of nav and gates the route). [code: Sidebar.tsx:188-190; App.tsx
RestaurantOnly]
Walkthrough
Reconstructed from POSPage source at
4cbcfd7.
Creating a takeout order
- From POS takeout mode, tap the Take Out button (visible when the outlet has takeout enabled and the user has create permission). On the plain counter view the same button is labelled Takeout alongside Dine-in and Delivery. [code: foodops.counter.app/src/pages/POSPage.tsx:4323-4329,4343-4348]
- The standard order-create flow opens with order type = takeout (no table requirement — table selection is a dine-in concern). [code: POSPage.tsx:3770-3791]
- Add items, confirm — the order lands in the takeout queue's Open tab and KOTs route to the kitchen as usual.
Tracking the queue
- The takeout queue shows pills: Open / Preparing / Ready / Picked / Cancelled with live counts. [code: POSPage.tsx:4374-4387]
- The kitchen advances items; when every item on a takeout order is served, the server auto-advances the parent order to Picked (status id 8) — that is the takeout-type terminal, the counterpart of dine-in's Served(5). [code: POSPage.tsx:744-768]
- The Picked tab lists both "picked up, not yet settled" and "picked + closed" orders; the badge sums the two buckets so the count matches the list. [code: POSPage.tsx:4375-4385]
- Settle payment as normal — as of
5167f45this no longer closes the order by itself (isPaidwas dropped as a terminal signal on the order card); a paid-but-open order keeps showing kitchen-lifecycle CTAs and needs an explicit Close step (surfaced from the order details panel via a newhandleCloseClick) before it renders as a closed card.
Key concepts
- Takeout mode, not a takeout page — the real queue is POSPage with
?mode=takeout;TakeoutPage.tsxat/takeoutis a placeholder shell as of4cbcfd7. [code: POSPage.tsx:168-170; TakeoutPage.tsx] - Picked (status id 8) — takeout's terminal operation status. Display name "Picked"; for non-delivery modes the "closed" tab semantics send
Closedto the server, while takeout's Picked tab filtersstatusId === 8 || operationStatus === 'picked'. [code: POSPage.tsx:678-679,2109,4028] - Auto-advance on item-served — item-level "served" events map to the type-appropriate terminal: takeout → Picked(8), dine-in → Served(5), delivery → no change. A guard prevents late kitchen events from regressing an order out of a terminal state. [code: POSPage.tsx:744-790]
- Status-id as source of truth — the client derives the canonical operation status from
restaurantOrderOperationStatusIdrather than trusting the API's status string (a historical server bug returned "open" for Picked orders, which made picked orders vanish from the Picked tab). [code: POSPage.tsx:684-709] - Offline-first identity — takeout orders created offline get a tempId and keep their card identity across the tempId → serverId swap on resync; the order reference itself is server-issued at create time. [code: POSPage "What changed" commits a4f0488/244ef17; hubits-api-sales OrderCreationService.cs:85-86]
- Order type id — takeout is
restaurantOrderTypeId = 2(dine-in 1, delivery 3). [code: POSPage.tsx:1812-1813]
Common questions
Q: How do I mark a takeout order as collected? A: Usually you don't have to — when the kitchen marks all items served the order auto-advances to Picked. You can also advance it from the order card. [code: POSPage.tsx:744-768]
Q: What's the difference between Ready and Picked? A: Ready (4) = kitchen finished plating/bagging, waiting at the counter. Picked (8) = customer collected. Auto-advance moves served-item orders to Picked; if your flow needs an explicit "waiting on shelf" stage, leave orders in Ready until handed over.
Q: Why does the Picked tab show orders I already closed? A: By design — the Picked tab's filter includes picked-then-settled orders, and the badge sums both buckets so count matches list. [code: POSPage.tsx:4375-4385]
Q: Can I create a takeout order without an open register session? A: No — orders need a session. The sidebar routes you to the Counter page with a takeout intent; opening a register carries you into takeout mode. [code: Sidebar.tsx:335-336; CounterPage.tsx:105]
Q: Do takeout orders need a customer name/phone? A: Customer attachment is optional and uses the standard customer selector on the order panel; nothing takeout-specific forces it (not found in current code as of 2026-06-11).
Q: Why does the old /takeouts URL not work?
A: The counter route is /takeout (singular), and even that page is a placeholder. Use the sidebar Takeouts item, which routes into the live POS takeout mode. [code: App.tsx:184]
Q: Does takeout appear for retail (salesmade) builds?
A: No — Takeouts is filtered out of the rail and the routes are gated RestaurantOnly for the salesmade brand. [code: Sidebar.tsx:188-190]
Q: Where do online pickup orders land? A: Online orders flagged pickup arrive over SignalR and appear in the same takeout queue, alongside counter-created takeouts. See Online Ordering Platform.
Q: What if the same register has takeout and delivery service both running? A: They are separate modes of the same session POS — switch via the sidebar (Takeouts / Deliveries). Order lists, tabs and counts re-filter per mode; the active tab resets to Open if the previous tab doesn't exist in the new mode. [code: POSPage.tsx:1389-1402]
Q: Is there a "Table" on a takeout order?
A: Takeouts normally have no table, but where a table name is present the kitchen ticket prepends "Table" for clarity (commit da7df48).
Q: Can a Picked order be re-opened? A: A terminal-state guard stops kitchen events from regressing it; manual correction goes through the order edit flow subject to the usual paid/closed restrictions. [code: POSPage.tsx:780-790]
Edge cases and known issues
- Placeholder
/takeoutpage — typing the URL directly gives a static page whose "New Takeout Order" button has no handler at4cbcfd7. Not a bug in the queue itself — use the sidebar path. [code: TakeoutPage.tsx:15-17] - Picked-count mismatch (fixed) — the Picked badge previously used the union
closedCountand could read "Picked 5" over an empty list; it now sumspickedCount + servedCountto match the list filter exactly. If you see a mismatch again it's a regression. [code: POSPage.tsx:4375-4385] - Stale status strings from the server — defended client-side by deriving status from the status id; a takeout order that "disappears" from Picked while showing in Open would indicate this defence failed. [code: POSPage.tsx:684-709]
- Offline-created takeouts — orders queue locally when offline and sync on reconnect; divergent edits land in the conflicts flow (
/conflicts). See the offline commits referenced in "What changed" below. - Legacy monolith Takeouts — the old
haafai.app.foodopsViews/Takeouts/**+TakeoutsController.csare retired at the July-10 launch; the counter app (POS?mode=takeout) is the sole canonical surface.
Related
- POS Takeaway Order — full step-by-step takeaway order creation walkthrough
- Deliveries — sibling queue for orders that go out
- Order Types — enabling takeout per outlet
- Kitchen Display System — the upstream of the Ready/Picked transitions
What changed since 2026-04-15 (NEW 2026-05)
The dedicated TakeoutPage.tsx route (/takeout) only received cosmetic design updates (commit 73b53ef 2026-04-24). However, the underlying takeout creation flow lives in POSPage.tsx with the Order Type = Takeout filter, which received substantive work since 2026-04-15:
Offline-first reliability — POSPage now generates a tempId for orders submitted offline, then preserves the OrderCard identity across the tempId → serverId swap on resync (
a4f0488,244ef17).Session scoping — orders carry
registerSessionIdso the takeout queue filters correctly across multiple open registers (0bb2729,05097a4,d5f2d6c).Stale-while-revalidate caching — session details and order action buttons load instantly from localStorage during connectivity drops (
68e0bf7)."Table" prefix on takeout/dine-in tables — KitchenTicket + POSPage prepend "Table" to table names for clarity (
da7df48).New / Open status consolidation — POSPage merges 'New' status into 'Open' for consolidated counts (
113041b).Conflict resolution — offline edits now flow through a conflict-resolution dashboard when reconciliation finds divergent server state (
88bd076,522968c).2026-08-22 — foodops.counter.app (VoidReasonSnackbarHost.tsx(+test), OrderSummary.tsx, OrderTotals.tsx, DiscardActionButtons.tsx(+test), OrderCard.tsx, DraggableOrderTicket.tsx,
d35a1ba..83f0da4, within this entry's tracked src/components/orders/** surface): the OrderSummary/OrderTotals computeCartTotals + customer-discount auto-apply threading, DiscardActionButtons' bg-white Discard button, OrderCard's whole-card-click/footer-stopPropagation/last:hidden separators/ribbon-pinned grip, and DraggableOrderTicket's h-5/self-start tweak are all identical to hunks already logged 2026-08-15 through 2026-08-18. Genuinely new since the entry's verified tip (fe34047): VoidReasonSnackbarHost's void-reason model was reworked from a single fixedVOID_REASON_CHIPS/VoidReasonenum to a per-workflow configurable list (VoidReasonChip[]passed viadetail.reasons, keyed by a newVoidReasonWorkflowenum with at leastDiscardandTableMovevariants, each with its ownDEFAULT_VOID_REASONSset — confirmed by a new test asserting a table-move prompt shows 'Table double-booked' and never 'Food quality issue'). More significantly, the snackbar now branches on a newdetail.reasonMandatoryflag: mandatory prompts still stack oldest-in-DOM/newest-in-front in the existing peek cascade, but OPTIONAL prompts now REPLACE rather than stack (only the newest ever shows) and gain a dismiss (X) button that clears the prompt with nopatchVoidReasoncall at all — i.e. an optional void reason can now be silently skipped, where previously (per all prior verification notes) every void-reason prompt was implicitly mandatory and had to be resolved via a reason chip.patchVoidReason's signature also changed from(entryId, reasonId, reasonText?)to(entryId, null, label), so reasons are now always passed as a label string rather than a numeric enum id. This entry's body still documents no void-bin/void-reason content at all, so nothing existing is contradicted — filing as NEW_INFO, extending the still-unfolded void-bin thread (first surfaced 2026-08-04). Worth folding in once confirmed live in takeout mode, alongside the thread's other open questions: which takeout-relevant actions produce a MANDATORY vs OPTIONAL void reason (e.g. does removing a takeout line item require a reason, or can it be dismissed?), and does a table-move-style reason list ever surface in a non-dine-in (takeout) context or is TableMove purely a dine-in workflow riding on this shared component. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-20 — foodops.counter.app (VoidReasonSnackbarHost.tsx, OrderSummary.tsx, OrderTotals.tsx, DiscardActionButtons.tsx(+test), OrderCard.tsx, DraggableOrderTicket.tsx, VoidReasonSnackbarHost.test.tsx,
d35a1ba..fe34047, within this entry's tracked src/components/orders/** surface): almost the entire diff is a re-surfacing of content already logged through 2026-08-18 at the same base range — the peek-stack cascade (PEEK_OVERLAP/PEEK_STEP_X, oldest-in-DOM/newest-in-front/older-inert-via-pointer-events) matches 2026-08-18 verbatim; OrderSummary.tsx/OrderTotals.tsx's computeCartTotals threading (resolvedServiceFeesRef/onServiceFeesResolved, getLoyaltyOverrideValue) and the customer-assigned-discount auto-apply path (gateCustomerDiscount/resolveCustomerDiscount, autoDiscountCustomerIdRef/customerDiscountRequestRef/pendingCustomerDiscountRef, syncCustomerDiscount, stripOrderLevelDiscount) match 2026-08-17/18; OrderCard.tsx's isExpandLoading removal + whole-card-click-opens-details + footer stopPropagation + last:hidden separator guard match 2026-08-17/18; DiscardActionButtons.tsx's bg-transparent→bg-white and DraggableOrderTicket.tsx's h-6→h-5/self-start match 2026-08-16. The one item not previously called out: the toast/card containers in VoidReasonSnackbarHost.tsx (the blocked-toast div, the snackbar-card div, and the VoidToast success/undo div) all gainedshrink-0, and the stale// Newest on top.comment was deleted (ordering has been oldest-first-in-DOM since 08-16/17).shrink-0is a minor flex-layout robustness fix — it stops the peek-stack's flex-column from compressing individual cards when the stack grows past viewport height and the outermax-h-[calc(100vh-6rem)] overflow-y-autowrapper (already logged 2026-08-14) starts scrolling — complementary to, not a change of, that scroll-cap fix. This is plausibly the diff's only content past the entry's last-verified tip (e2fe2fc); it may also have already been present un-remarked in the 08-18 diff, since it's non-behavioral. Either way it touches only the still-unfolded void-bin/void-reason thread (first surfaced 2026-08-04) that this entry has never brought into its body, and answers none of that thread's open questions (enableVoidBin on this tenant, reviewer visibility, whether editing a takeout order's items logs a void-bin entry, or whether the customer-discount auto-apply is visibly distinguished from a manual discount). Nothing in this diff contradicts any claim currently in the entry. Not worth folding in on its own; continue holding this note alongside the accumulated void-bin/customer-discount/card-click questions pending a live Playwright pass in takeout mode. (auto-applied by the truth pipeline; adversarially gated)2026-08-18 — foodops.counter.app (VoidReasonSnackbarHost.tsx(+test), OrderSummary.tsx, OrderTotals.tsx, DiscardActionButtons.tsx(+test), OrderCard.tsx, DraggableOrderTicket.tsx,
d35a1ba..e2fe2fc, within this entry's tracked src/components/orders/** surface): mostly a re-surfacing of threads already logged in this base range — DiscardActionButtons' bg-transparent→bg-white Discard button and DraggableOrderTicket's h-6→h-5/self-start tweak are the identical hunks already logged 2026-08-16, and OrderCard's isExpandLoading-removal/whole-card-click-opens-details/footer-stopPropagation change is the identical hunk already logged 2026-08-17 as NEW_INFO (still unconfirmed live in takeout mode). Two items are genuinely new. (1) VoidReasonSnackbarHost.tsx's void-reason prompt stack, previously described (2026-08-16) as a 'staircase cascade' with titles peeking out, is now shown as a 'peek stack' (constants renamed CASCADE_OVERLAP/CASCADE_STEP → PEEK_OVERLAP=102/PEEK_STEP_X=14): every queued reason-prompt now renders as a FULL card (not a title sliver), stacked oldest-to-newest in the DOM, newest painted in front and the only interactive (non-null pointer-events) card, older cards pulled up behind via negative marginTop and staggered right via marginRight, and made inert (pointer-events:none) — confirmed by the new test asserting both cards render full reason-chip buttons ('Customer changed mind', 'Other') simultaneously, not just peeking titles. This refines rather than contradicts the 2026-08-16 note but corrects the 'titles peeking' framing. Still within the void-bin thread this entry has never folded into its body (open questions on enableVoidBin/reviewer visibility/edit-logging remain unanswered by this diff). (2) OrderSummary.tsx/OrderTotals.tsx gained a NEW, previously-unlogged feature layered on top of the already-tracked computeCartTotals synchronous-totals refactor (2026-08-15/17): a customer-assigned-discount auto-apply path (new imports gateCustomerDiscount/resolveCustomerDiscount from services/discount/customerDiscount, new refs autoDiscountCustomerIdRef/customerDiscountRequestRef/pendingCustomerDiscountRef, and a new syncCustomerDiscount function invoked 'from the picker only'). Per the code's own comments, selecting a customer with an assigned discount now auto-applies it as the bill discount, but only ever touches a discount THIS path applied — a cashier-entered manual discount or active loyalty redemption blocks the auto-apply, and manually changing the discount (handleDiscountChange) clears the auto-apply tracking so a later customer switch won't fight it. This applies to all order types sharing OrderSummary, including takeout, and the entry currently documents no discount behavior at all, so nothing existing is contradicted — filed as NEW_INFO. Also newly visible in OrderCard.tsx (not in the 2026-08-17 note): when a NEW/LATE/TRANSFERRED ribbon is showing, the leadingSlot (drag-grip mount point, still not confirmed wired to an actual grip in takeout mode) is now pinned to an absolute top-left corner above the order-type icon instead of sitting inline on the header row, and the footer's icon-button separators gained a last:hidden guard so a lone separator doesn't dangle at the card edge when no icon buttons follow it — both cosmetic, no behavior change. Worth folding in once confirmed live in takeout mode: does selecting a customer with an assigned discount on a takeout order actually auto-apply it to the bill, and is there a visible indicator distinguishing it from a discount the cashier set manually? (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-17 — foodops.counter.app (VoidReasonSnackbarHost.tsx, OrderSummary.tsx, OrderTotals.tsx, DiscardActionButtons.tsx(+test), OrderCard.tsx, DraggableOrderTicket.tsx, VoidReasonSnackbarHost.test.tsx,
d35a1ba..5c591cc, within this entry's tracked src/components/orders/** surface): almost entirely a re-surfacing of content already logged earlier in this same base range — the VoidReasonSnackbarHost.tsx staircase-cascade implementation (CASCADE_OVERLAP/CASCADE_STEP, auto-scroll-to-newest, capped+scrollable stack) matches the 2026-08-16 note verbatim, now backed by two new unit tests confirming oldest-to-newest DOM order and z-index/margin stacking but adding no new behavior; the OrderSummary.tsx/OrderTotals.tsx computeCartTotals refactor (synchronous save-time totals recompute replacing the async-ref snapshot) matches the 2026-08-15 note, with one added plumbing detail — a new resolvedServiceFeesRef/onServiceFeesResolved callback lifts OrderTotals' resolved service-fee array up to OrderSummary so the save-time computeCartTotals call has the same serviceFees array the live cart used — but this is implementation detail supporting the already-logged synchronous-totals fix, not new user-facing behavior; DiscardActionButtons.tsx's bg-transparent→bg-white Discard-button change matches 2026-08-16; and DraggableOrderTicket.tsx's h-6→h-5 + self-start tweak is the identical hunk already logged 2026-08-16, not a new appearance. The one genuinely new item: OrderCard.tsx removed its isExpandLoading prop ('No longer used — the expand icon button was removed; card click opens details instead') and made the entire card div clickable (cursor-pointer, onClick={() => onExpand?.(normalizedOrder.id)}), with the footer given onClick={(e) => e.stopPropagation()} so clicks on its action-button area don't also trigger the card-open handler. This is a real interaction-model change — order cards no longer need a dedicated expand icon button; tapping anywhere on the card body (outside the footer's buttons) now opens order details. This entry documents no explicit mechanism for opening order details from a queue card today, so nothing existing is contradicted (filed as NEW_INFO, not NEEDS_EDIT). Worth folding into the Walkthrough or a new Common Question once confirmed live in takeout mode: does tapping a takeout order card (outside its footer buttons) reliably open the order details panel, and was there previously a separate expand icon staff relied on that's now gone? (auto-applied by the truth pipeline; adversarially gated)2026-08-16 — foodops.counter.app (VoidReasonSnackbarHost.tsx, OrderSummary.tsx, OrderTotals.tsx, DiscardActionButtons.tsx(+test), OrderCard.tsx, DraggableOrderTicket.tsx,
d35a1ba..c37cb61, within this entry's tracked src/components/orders/** surface): the OrderSummary.tsx/OrderTotals.tsx hunks in this range are the identical computeCartTotals synchronous-totals-at-save change already logged 2026-08-15 (same commit, no new content). The VoidReasonSnackbarHost.tsx change is genuinely new since 2026-08-14: the void-reason prompt stack is no longer a simple capped scrollable list but a 'staircase cascade' — newest reason-prompt renders fully visible in front (bottom-right), older unresolved prompts step up-and-left behind it with titles still peeking out (CASCADE_OVERLAP/CASCADE_STEP), auto-scrolled to keep the newest in view, still capped to max-h-[calc(100vh-6rem)] and independently scrollable. This still only touches the same void-bin/void-reason thread this entry has never folded into its body (first surfaced 2026-08-04, most recently 2026-08-14) and answers none of that thread's open questions (enableVoidBin on this tenant, reviewer visibility, whether editing a takeout order's items logs a void-bin entry). Separately, a DraggableOrderTicket.tsx component surfaces for the first time in this entry's tracked diffs, and it's a real @dnd-kit useDraggable grip (listeners/attributes wired, only a minor size/alignment tweak in this diff: h-6→h-5, added self-start) — this is the first hunk actually showing a working drag-grip element, extending (not yet resolving) the 'drag-a-ticket-into-Takeout' plumbing thread tracked since 2026-08-04/08-06/08-07/08-09; no hunk here shows it mounted inside POSPage's order board or wired to DiscardActionButtons' onTakeout, so the interaction remains unconfirmed live. DiscardActionButtons.tsx's Discard button also got a cosmetic bg-transparent→bg-white change (Figma 2955:23723 per the new test), and OrderCard.tsx now pins its leadingSlot grip to an absolute top-left corner position when a NEW/LATE/TRANSFERRED ribbon is present instead of sitting inline — both are visual polish on already-tracked, still-unwired plumbing. Nothing in this diff contradicts any claim in the entry body (which documents no void-bin or drag-to-takeout content). Worth folding in only once confirmed live: does the void-reason cascade behave correctly under a long queue in takeout mode, and is DraggableOrderTicket's grip actually mounted/functional anywhere in the takeout order board yet? (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-15 — foodops.counter.app (OrderSummary.tsx, OrderTotals.tsx,
d35a1ba..5cbfe1a, within this entry's tracked src/components/orders/** surface; the VoidReasonSnackbarHost.tsx hunk in this same range is the identical scroll-cap fix already logged 2026-08-14, no new content there). A new shared helper, computeCartTotals(), now backs both the live cart display (OrderTotals.tsx's displayTotals, refactored from an inline OrderCalculationService call) and the save-time offline snapshot (OrderSummary.tsx's handleSave path). Previously, saving an order wrote_offline_totalAmount/_offline_taxTotal/_offline_serviceChargeTotal/etc. fromlastCalculatedTotalRef/receiptDetailsRef— values populated by an earlier async effect that could lag a fast edit-then-Save/Print. Now these fields are recomputed synchronously from the livecurrentOrderItemsat save time (with the old refs kept only as a fallback), per the commit's own subject: 'compute order totals synchronously at save so mid-flow receipts match the cart.' This applies to all order types including takeout, and is relevant to this entry's 'Offline-first identity' claim about offline-created takeout orders and their stored order data — the fix specifically targets the offline* fields the entry references only indirectly (via the tempId/serverId identity claim, not totals). Nothing here contradicts an existing claim; the entry makes no assertion about receipt/total accuracy at save. Worth folding in only if confirmed live and relevant to takeout specifically: does rapid-editing a takeout order's cart then immediately hitting Save/Print now reliably show the just-edited total on the receipt, where before it could occasionally show a stale one? (auto-applied by the truth pipeline; adversarially gated)2026-08-14 — foodops.counter.app (VoidReasonSnackbarHost.tsx,
d35a1ba..5b426a9, within this entry's tracked src/components/orders/** surface): a small UX fix to the void-reason snackbar stack already tracked in this entry's void-bin thread (first surfaced 2026-08-04, extended 2026-08-06/07/09). The reason-prompt stack is now capped to viewport height and made independently scrollable (max-h-[calc(100vh-6rem)] overflow-y-auto), with the fix's own comment explaining the prior behavior: an uncapped stack of queued void-reason prompts (newest on top) could grow off the top of the screen with no way to reach older, still-unanswered prompts underneath. This is a robustness fix to an interaction surface, not a new capability, and doesn't touch or answer any of the open questions already logged against this thread (whether this tenant hasenableVoidBinon, whether a reviewer role is visible, whether editing a takeout order's items logs a void-bin entry). Not folded into the entry body — the entry still documents no void-bin content, consistent with prior passes. Worth folding in only once the underlying void-bin flow itself is confirmed live for a takeout-mode session: does a long queue of void reasons on a busy register now visibly scroll instead of clipping off-screen? (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-09 — hubits-api-sales (OrdersController.cs,
5889642..cdb3b32, within this entry's tracked OrdersController.cs surface): a new server-side auto-close path,TryCloseIfTerminalAndSettledAsync, fires inside ChangeStatus whenever a status transition lands an order on its type's terminal SERVICE status (dine-in Served / takeout Picked / delivery Delivered) AND the order is already settled (paid) — the comment explicitly names this as the mirror of an existing payment-side trigger,OrderPaymentController.IsInTerminalServiceStatus, so whichever of {settle, reach-terminal} happens second is what actually closes the order. This is relevant to this entry's Walkthrough step 4 ('Settle payment as normal — as of 5167f45 this no longer closes the order by itself... needs an explicit Close step... before it renders as a closed card'), which was verified against the counter.app FRONTEND order-card rendering only. This backend addition means that for a takeout order paid BEFORE the kitchen finishes (pay-first-then-auto-advance-to-Picked), or one whose payment lands right as/after it reaches Picked, the server itself now closes the order — no explicit Close click required — which the frontend's status-id-as-source-of-truth card should then render as closed automatically. Nothing here directly contradicts the entry's prose (that prose describes the client no longer treating a bare isPaid flag as terminal for card rendering, not the server's own close state), so filing as NEW_INFO rather than NEEDS_EDIT. The identical backend hunk was already logged against the sibling entry FoodOps/counter/pos-takeaway-order.md on 2026-08-08 (and CONTEXT.md's 2026-08-07 night note) as NEW_INFO with the same non-contradiction reasoning — this is the first time it's being folded in against takeouts.md specifically. Worth verifying live before editing Walkthrough step 4 or the 'Can a Picked order be re-opened?' Q&A: does paying a takeout order before it reaches Picked, followed by the kitchen-served auto-advance, now render the order as a closed card with no staff action, contradicting the documented explicit-Close requirement for that ordering; and does a Picked-but-unpaid order still require the explicit Close step once settled after the fact. (auto-applied by the truth pipeline; adversarially gated)2026-08-09 — foodops.counter.app (Sidebar.tsx, DiscardActionButtons.tsx, OrderCard.tsx, OrderDetailsPanel.tsx, OrderSummary.tsx, VoidBinPanel.tsx, VoidReasonSnackbarHost.tsx,
2e88679..7058493): almost entirely a re-surfacing of threads already fully logged by the 2026-08-04/06/07 passes at the same base commit — the AdminAppRedirectDialog is confirmed still scoped toitem.externalrows only (no change to the Takeouts item's documented internal routes, reconfirming prior no-impact findings), DiscardActionButtons' Take-Out/Delivery drop targets are unchanged and still not shown wired into POSPage's order board, and the void-bin cancel/edit-logging/VoidBinPanel review UI (OrderDetailsPanel.tsx, OrderSummary.tsx, VoidBinPanel.tsx, VoidReasonSnackbarHost.tsx) is identical to what 2026-08-07 already logged as reaching full form. One incremental detail not previously called out: OrderCard.tsx's per-item action button (rendered per line item, icon keyed off order type incl.isTakeoutItem) hadisPaiddropped from itsisDisabledcheck, alongside the already-documented Accept-button isPaid removal (acceptDisabled/rejectDisabled, matching the 2026-08-06 note) — extending the 'payment is not an accept gate' pattern from the order-level Accept action to this per-item button as well. The diff hunk doesn't show the button's onClick/label, so its exact function (mark-item-served vs. something else) isn't confirmed here. Nothing in this diff contradicts any existing claim in the entry. Worth folding in once confirmed live, alongside the already-pending Accept/Reject and void-bin questions: what does this per-item action button do in takeout mode, and does it now stay clickable on a paid-but-not-closed takeout order the same way Accept does? (auto-applied by the truth pipeline; adversarially gated)2026-08-07 — foodops.counter.app (Sidebar.tsx, DiscardActionButtons.tsx + test, OrderCard.tsx, OrderDetailsPanel.tsx, OrderSummary.tsx, VoidBinPanel.tsx [new], VoidReasonSnackbarHost.tsx [new],
2e88679..fe749d8): largely the same threads already logged 2026-08-04/2026-08-06 reaching fuller completion, plus one genuinely new piece. (1) VoidBinPanel.tsx is a full new manager-review UI for void-bin entries — groups voided tickets/items by event, shows reason chips ('Reason Pending' when unset) and a permission-gated Approve/Reject action pair (canReview), with Approved/Rejected status badges once processed. This is new: prior notes only established that cancelling withenableVoidBinon skips the confirm dialog and captures a reason via snackbar afterward — now there's a whole downstream review/approval step nobody had visibility into before. (2) OrderSummary.tsx's void-bin plumbing now also covers order EDITS, not just cancellation: whenenableVoidBinis on, saving an edited order snapshots the pre-save line items (captureOrderLinesForVoid) and logs the diff against post-save lines (logOrderEditVoids) — so removing/decreasing a line item on an open takeout order under this flag is now void-bin-audited the same way a full cancel is. (3) DiscardActionButtons.tsx (previously only a name+test stub per the 2026-08-04 note) is now a real@dnd-kituseDroppablecomponent with working Discard/Delivery/'Take Out' (DROP_TAKEOUT) drop zones and hover-scale/badge feedback — but no hunk here (or in prior diffs) shows it mounted inside POSPage's order board, nor OrderCard's newleadingSlot/itemGripslots (2026-08-06) wired to it, so the drag-a-ticket-into-Takeout interaction is still not a confirmed live path in takeout mode. (4) The Sidebar.tsx admin-nav confirmation dialog (AdminAppRedirectDialog,handleItemNavigate/handleNavAnchorClick) reaches its full form here but remains scoped toitem.externalrows only — it still doesn't touch the Takeouts item's documented internal routes (/sessions/{id}/pos?mode=takeout,/counter?intent=takeout), reconfirming the 2026-08-06 no-impact finding. Nothing here contradicts an existing claim in this entry. Worth folding in once confirmed live, alongside the already-pending void-bin questions: does this tenant haveenableVoidBinon for takeout mode, is there a visible reviewer role for void-bin Approve/Reject in the counter UI, and does editing a takeout order's items whileenableVoidBinis on visibly log a void-bin entry the same way a cancel does? (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-06 — hubits-api-sales (OrdersController.cs, new CustomerOrderingApiNotificationService.cs,
5889642..9351716): a new server-side webhook to foodops.ordering.api (the customer ordering app's backend) now fires on two paths this entry documents. (1) ChangeStatus callsNotifyOrderStatusChangeAsync(order, orgId, request.StatusId)for every status change, and the service'sMapStatusswitch includesPicked => "Picked"— so a transition into this entry's takeout-terminal status (Picked, id 8), however it's triggered (auto-advance or manual), now also fires a customer SMS/SignalR notification via this endpoint, where previously (per the service's own doc-comment) counter.app tried to call this webhook directly from the browser and was silently 403'd because the endpoint requires a secretServiceApiKeyskey the browser can't hold — "no payment SMS ever went out." This is now fixed by moving the call server-side. (2) CancelOrder captureswasRejectedBeforeAccept(true iff the order's operation status was stillNew, i.e. never accepted) before the cancel write, and — only in that case — notifies the ordering API with statusRejected; a cancel of an already-accepted order stays silent. This directly confirms and extends the 2026-07-30 verification-history note's open question about the Accept/Reject flow: rejecting a pending order is indeed just Cancel-before-Accept under the hood, and it now additionally texts the customer 'Rejected' (distinct from a plain counter-side cancel/void of an accepted order, which sends nothing). Nothing in the entry is contradicted — the entry makes no existing claim about customer notifications on status change or cancel — so this is additive. Worth folding into 'Where do online pickup orders land?' and the Picked-related Common Questions once confirmed live: does an online-flagged (isMobile) pickup order's customer actually receive an SMS when staff mark it Picked or Ready, and does rejecting a not-yet-accepted online takeout order visibly text 'Rejected' to the customer (requiresCustomerOrderingApi:BaseUrl/ApiKeyto be configured per the service's own guard logging, so also worth checking those are set for this tenant). (auto-applied by the truth pipeline; adversarially gated)2026-08-06 — foodops.counter.app (OrderCard.tsx, Sidebar.tsx, DiscardActionButtons.test.tsx,
2e88679..3f286e8, extending the already-logged 2026-08-04 void-bin diff to a later commit): two incremental changes to features already flagged in prior verification notes as unconfirmed/not-yet-live, neither contradicting anything currently documented. (1) OrderCard now acceptsleadingSlotanditemGriprender-prop slots at the header row and each item row, explicitly commented as drag-grip mount points for 'the dine-in board' — this is the next piece of plumbing toward the drag-to-convert-to-Takeout interaction first surfaced by DiscardActionButtons.tsx (showTakeout/DROP_TAKEOUT, samehgi-shopping-bag-03icon this entry's Key Concepts already cite) in the 2026-08-04 note. Still no hunk shows an actualuseDraggablegrip element occupying these slots, nor OrderCard/POSPage wired to DiscardActionButtons'onTakeout— so the drag-a-dine-in-ticket-into-a-takeout-order path remains unconfirmed, not yet a documentable user flow. (2) Accept is no longer blocked byisPaid(both the button'sacceptDisabledand the click-handler guard dropped it), with a new comment explaining staff can now accept an online order that was paid at the counter before Accept was pressed; Reject stays blocked once paid. This extends the Accept/Reject 'Pending' gate flagged 2026-07-30 (still absent from this entry and from its 'Where do online pickup orders land?' Q&A) — worth folding in together once confirmed live: does a counter-created takeout order ever surface as Pending/requiring Accept, and can a paid pending order now be accepted from the takeout queue. The Sidebar.tsx admin-redirect-confirmation-dialog change in this diff only touchesitem.externalnavigation (e.g. the Admin app link) and does not intersect the Takeouts item's internal routes (/sessions/{id}/pos?mode=takeout,/counter?intent=takeout) already documented in 'How to access' — no impact. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-04 — foodops.counter.app (OrderDetailsPanel.tsx, OrderSummary.tsx, DiscardActionButtons.tsx + test, VoidBinPanel.tsx, VoidReasonSnackbarHost.tsx,
2e88679..5167f45): a session-scopedenableVoidBinflag (read viagetCurrentSession()) changes two flows this entry touches. (1) Cancel: with the flag on, OrderDetailsPanel'shandleCancelOrderskips the existing confirm dialog and calls a newvoidOrderToBin()action, optimistically flips the order to 'cancelled', and only collects a reason afterward via a snackbar (VoidReasonSnackbarHost) — a real change to how an order lands in this entry's Cancelled tab when the flag is on, though the entry makes no prior claim about a confirm dialog so nothing existing is contradicted. (2) A newDiscardActionButtonscomponent exposes drop targets labelled 'Discard', 'Delivery' and 'Take Out' (showTakeoutprop,DROP_TAKEOUTid, iconhgi-shopping-bag-03— the same icon this entry's Key Concepts section already cites for takeout order-type styling), implying tickets can be drag-converted into a takeout order; only the component + its unit tests were added in this diff, no hunk shows it wired into POSPage's order board yet, so treat as unconfirmed. Also, OrderSummary.tsx's line-item remove/decrease permission gates now bypass the normal permission check entirely whenenableVoidBinis on. Worth folding in once confirmed live in takeout mode: does this tenant haveenableVoidBinon, does cancelling a takeout order skip the confirm dialog, and is the drag-to-Take-Out target reachable from the takeout queue or only from a dine-in board? (auto-applied by the truth pipeline; adversarially gated)2026-07-30 — foodops.counter.app (OrderCard.tsx, OrderListItem.tsx, POSPage.tsx,
c7d7801..5ac2982): new-order Accept/Reject workflow (previously in OrderCard grid view only) has been ported to OrderListItem (list view) and wired into POSPage via a newonOptimisticStatusChangeprop, so both order-card layouts now behave identically for orders in the 'new' operation status. Three user-facing changes worth folding into this entry once confirmed live in takeout mode: (1) the individual order-status pill for a 'new' order is relabeled 'New' → 'Pending'; (2) a 'new' order whoseisMobileflag is set (i.e. arrived from the online ordering app) now gets a blinking red border + red elapsed-time cue so staff notice it, distinct from the plain 'New' orders created at the counter; (3) Reject is implemented by calling the existing/orders/cancelendpoint withCancelRemarks: 'Rejected by counter'— i.e. rejecting a pending order routes it into the same cancel path already covered by this entry's Cancelled tab and by the 2026-07-22 verification-history note onSalesOrders.Delete-gated cancellation. This is directly relevant to the 'Where do online pickup orders land?' Q&A, which currently says online pickup orders 'arrive over SignalR and appear in the same takeout queue' with no mention of an accept/reject gate. Not filed as NEEDS_EDIT because the diff doesn't prove this accept-step is new behavior (the commit subject implies Accept/Reject already existed for the OrderCard/grid view before this diff and is only now being extended to the list view + given new visual cues) — nor does it prove whether counter-created takeout orders ever pass through 'new'/Pending or whether this gate is exclusive to online-app orders. Verify live in takeout mode before editing: does a counter-created takeout order ever show as Pending requiring Accept, or is that state reserved for online-ordering-app arrivals (isMobile-flagged)? (auto-applied by the truth pipeline; adversarially gated)2026-07-29 — hubits-api-sales (OrdersController.cs,
9f561de..a26fdc7): CreateOrder gained a second, independent duplicate-create guard keyed onCounterLocalReference(30-minute window), sitting ahead of the existing RequestId-based idempotency check. It exists to close a gap the RequestId reservation can miss — a client retry that outlives the 120s StaleReservationSeconds gets its RequestId reservation reclaimed and re-runs order creation for the same logical local order (real incident: SO-12795/SO-12799, 2026-07-28, ~120s apart). When a match is found, the server returns the ORIGINAL order (id/reference/version) instead of creating a new one, logging a warning. This is directly relevant to this entry's 'Offline-first identity' claim and the 'Offline-first reliability' bullet in 'What changed' — it's a server-side complement to the client-side tempId → serverId swap: a takeout order created offline and retried after a long connectivity drop now can't fork into two server orders even if the client-side RequestId path fails to dedupe it. No existing claim is contradicted (the entry doesn't currently describe server-side duplicate protection, only that 'the order reference itself is server-issued at create time'). Worth a line in 'Offline-first identity' once confirmed whether the counter app surfaces any signal when a duplicate-create is silently deduped server-side (e.g. does the client see any difference between 'my order was created' and 'the server matched an existing one'), or whether it's fully transparent to the user. (auto-applied by the truth pipeline; adversarially gated)2026-07-22 — hubits-api-sales (OrdersController.cs,
86e7d20..f26ba1f): CancelOrder now enforces theSalesOrders.Deletepermission server-side before cancelling any order (checked via a direct org-scopedUserRoleslookup, not the 3-min-cachedUser.IsInRole, so it also covers the API-key path). A user/token lacking that role now gets HTTP 403 ("You do not have permission to cancel orders") instead of the cancel succeeding. This applies to order cancellation generically (dine-in, delivery, and takeout), so it's relevant to the Cancelled tab documented in this entry — moving a takeout order to Cancelled now requires the caller to hold SalesOrders.Delete. No existing claim in the entry is contradicted (the entry doesn't currently state any permission requirement for cancelling), so this is additive: worth a line in 'How to access' or a new Common Question noting cancel requires SalesOrders.Delete, once confirmed against the counter-app UI (does the Cancel action surface a permission-denied state, or hide the button entirely for users without the role?). (auto-applied by the truth pipeline; adversarially gated)2026-07-10 — foodops.counter.app (Sidebar.tsx,
6885e57..e4e7aaa): the sidebar's desktop-rail and mobile-drawer nav rows were converted from<button onClick>to real<a href>elements, with a newresolveItemHref()helper computing the same destinations the entry already documents (session open →/sessions/{id}/pos?mode=takeout; no session →/counter?intent=takeout), andhandleNavAnchorClickexplicitly lets Ctrl/Cmd/Shift/Alt-modified clicks fall through to native browser handling. Net effect: the 'Takeouts' sidebar item (and other session-scoped nav rows) can now be opened in a new tab via middle-click, Ctrl/Cmd+click, or the browser's right-click 'Open in new tab' — previously impossible since the rows were plain buttons. Destination URLs are unchanged, so no correction to existing claims is needed; this is additive to the 'How to access' section only. No other diff hunks in this range (icon-library swap Hugeicons → lucideIconcomponent across ApplyDiscountModal/CategoryGrid/CollectCreditPaymentButton/CounterSettingsDropdown/DriverAssignmentModal/ItemDetailsModal/KOTModal) touch any surface this entry documents. (auto-applied by the truth pipeline; adversarially gated)2026-06-29 — codebase (July-10 launch re-verify;
foodops.counter.app@32669a9main,hubits-api-sales@19190b9main). UPHELD:/takeoutroute page is still a placeholder (TakeoutPage.tsx, 24 lines, "New Takeout Order" heading with no working handler); the real queue is POS?mode=takeout(Sidebar.tsx:95-96takeout context, navigates/counter?intent=takeoutwith no session at:339); Picked is the takeout terminal —statusId === 8 → 'picked'(POSPage.tsx:721), item-served auto-advance to Picked(8) for takeout / Served(5) for dine-in (:760-797), status-id-as-source-of-truth defence (:703-721),pickedCount/servedCountbadge sum (:107-108); salesmade retail gating of Takeouts (Sidebar.tsx:246). Order reference still server-issued at create viaGetAndUpdateNextNumber(SalesOrders)(OrderCreationService.cs:86,248), so the offline tempId → serverId behaviour holds. CORRECTED: dropped the retiredhaafai.app.foodopslegacy-Takeouts block fromsource_repos/verified_against_commitand reframed the "Legacy monolith Takeouts" edge case as retired-at-launch. Frontmatter SHAs + last_verified bumped.
Warning: Code-verified 2026-05-04 · codebase only (auth.json stale) Verified_by:
codebase· Last verified: 2026-05-04 Glob curated tosrc/pages/TakeoutPage.tsx;src/pages/POSPage.tsx;src/components/orders/**(counter.app) andHaafai.Restaurant.App/Views/Takeouts/**;Haafai.Restaurant.App/Controllers/TakeoutsController.cs(legacy monolith). Counter.app has substantive activity in the broader takeaway flow (POSPage); legacy monolith Takeouts views/controllers have been dormant since watermark. Entry remains a stub awaiting playwright walkthrough; "What changed" section derived from commit messages only.
Note: Code re-verified 2026-06-04 · hubits-api-sales@5274891 + foodops.counter.app@e002374 (main) Verified_by:
codebase· Last verified: 2026-06-04 VERIFIED — NO USER-FACING CHANGE. Re-checked against the 2026-06-03 refresh drivers: commit2489d1fis a backendSalesOrderLineItemTax.Id/ concurrency-logging fix on order edit (not an order-number change); the takeout order reference is still server-issued at create time (OrderCreationService.cs:85-86), so the offline tempId → serverId behaviour referenced in "What changed since 2026-04-15" is unchanged. The multiple-orders-per-table gate (AllowMultipleActiveOrdersPerTable) is dine-in-only and does not affect takeout. Entry remains astubawaiting a playwright walkthrough of the/takeoutsqueue; no content correction needed this pass.
- 2026-06-11 — codebase: promoted stub → draft against
foodops.counter.app@4cbcfd7+hubits-api-sales@5d917e5. Documented the real queue surface (POS?mode=takeout, tabs Open/Preparing/Ready/Picked/Cancelled), the Picked(8) terminal status, the item-served → Picked auto-advance with terminal-state regression guard, the status-id-as-source-of-truth defence, and the Picked-badge bucket fix. CORRECTIONS vs old stub: route is/takeout(singular) and that page is a placeholder shell — the sidebar routes to the session POS instead; "/takeouts" never existed in counter.app. Confidencemedium; UI labels pending Playwright walk.
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.