NEW stack only (2026-06-29, July-2026 launch). The legacy
haafai.app.foodopsBlazor monolith (and its/menucategoriespage) is retired at launch. Menu-group authoring is the admin app (foodops-admin-app, admin.foodops.io — Catalog → Menu Groups / Menu Order) backed by the sales API (MenuCategoriesApiController) for menu-group CRUD and the inventory API (CategoriesApiController) for category sort-order; the counter consumes menus viaOrderMenuController. Anyhaafai.app.foodops/Haafai.Restaurant.Appcitation below is historical (retired surface).
Plain-English summary
A Menu Group (also called a "menu") bundles product categories into a menu that the POS serves — for example a Breakfast menu containing the Hot Drinks and Pastries categories, and an All-Day menu containing everything. The hierarchy is: menu group → categories → items. One menu group is the default: the POS auto-loads it when a cashier starts an order, and the cashier can switch to another menu with the Switch Menu dropdown. Menu groups are created and managed in the admin backoffice (a card grid with add/edit/delete), and their on-screen ordering — along with category and item ordering — is arranged on a drag-and-drop Menu Order page.
What changed since 2026-04-15 (NEW 2026-05)
The authoring side for Menu Groups (/menucategories page in haafai.app.foodops, MenuCategoriesController.cs + Views/MenuCategories/**) had no commits since the watermark — group creation, category assignment, and menu-group → category mapping behavior is unchanged at the controller level.
The rendering side on the POS counter saw a UI redesign over 2026-04-21 → 2026-04-24:
CategorySidebar.tsxgotflex-1andmin-h-0classes for better layout handling (715180d2026-04-24).CategorySidebar,MenuContentArea,MenuSelector, andProductGridall received "design updates" (73b53ef2026-04-24) and "order page updated" (154ccfc2026-04-24).- Order-create flow UI fixes (
ffa9b052026-04-21) touched the category sidebar and menu rendering.
Net effect for staff/cashier-facing view: the visual treatment of Menu Group → Category navigation on the POS may differ from older screenshots in this KB.
When you'd use this
- Setting up a menu for a new outlet
- Reorganising the Counter layout for faster ordering
- Creating time-of-day menus (breakfast vs lunch) the cashier can switch between
- Choosing which menu the POS opens on by default
How to access
- Admin backoffice (canonical):
/catalog/groups— page "Menu Groups" ("Group product categories into menus that can be served at outlets."). [code: foodops-admin-app/apps/admin/src/App.tsx:270; src/pages/menu/MenuGroupsPage.tsx:81-86] - Menu ordering:
/catalog/order— drag-and-drop sort for menu groups, categories and items. [code: App.tsx:274; MenuOrderPage.tsx] - Legacy back-office:
/menucategoriesinhaafai.app.foodops— retired at the July-2026 launch (decommissioned with the monolith). - POS (consumption): the Switch Menu dropdown + category sidebar inside order create/edit.
Walkthrough
Creating a menu group (admin app)
Verified against
foodops-admin-app@77ec95f.
- Open Catalog → Menu Groups. Existing groups show as cards: name, a Default badge (if applicable), an Active/Inactive badge, the category count, and up to six category-name chips (+N more). Search filters by group or category name. [code: MenuGroupsPage.tsx:153-226]
- Click Add menu group. The form dialog collects: Name (e.g. "Breakfast menu"), a Default menu toggle ("Used as the default when no specific menu is chosen."), and a Categories multi-select (searchable; validation: "Select at least one category"). [code: MenuGroupFormDialog.tsx:140-185,101]
- Save. The API creates the menu (
POST /api/v1/menu-categories) active by default; if you marked it default, every other menu group's default flag is cleared — there is exactly one default per organisation. Menu caches are invalidated so the POS picks the change up. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/MenuCategoriesApiController.cs:215-270] - Edit via the pencil icon (same dialog,
PUT /{id}); delete via the trash icon → "Delete menu group?" confirmation. Delete is a hard delete — the group, its category links and its operation logs are removed (items and categories themselves are untouched). [code: MenuCategoriesApiController.cs:344-380]
Arranging menu order (admin app)
- Open Catalog → Menu Order. Three sections list menu groups, the selected group's categories, and a category's items, each sorted by
sortOrder. [code: MenuOrderPage.tsx:80-140] - Drag rows to reorder. Category and item order persist via the inventory API (
PUT /api/v1/categories/sort-order) and menu-item sort endpoints. [code: hubits-api-inventory CategoriesApiController.cs:524] - Known gap (STILL PRESENT on prod
19190b9): the menu-group reorder callsPUT /v1/menu-categories/sort-orderon the sales API, but no such endpoint exists inMenuCategoriesApiController— at HEAD it exposes onlyGET/GET {id}/GET product-categories/POST/PUT {id}/DELETE {id}(nosort-orderroute) — so menu-group drag-reorder fails server-side (categories and items are unaffected). [code: foodops-admin-app/apps/admin/src/services/menuGroupService.ts:52-58 vs hubits-api-sales/Hubits.Sales.Api/Controllers/MenuCategoriesApiController.cs @ 19190b9 (no sort-order endpoint)]
How the POS consumes menu groups
- On order create/edit the counter fetches the org's active menus (
GET /orders/restaurant-menus) and auto-selects the default one. [code: foodops.counter.app/src/hooks/useOrderCreate.ts:439; hubits-api-sales OrderMenuController.cs:253-302] - The cashier can change menus via the Switch Menu dropdown (MenuSelector). [code: src/components/orders/MenuSelector.tsx:24]
- Selecting a menu loads its categories + items (
GET /orders/menu-data?menuId=...); categories render in the category sidebar/pills ordered by the categorySortOrder. [code: orderApiService.ts:226; OrderMenuController.cs:304-349; MenuCategoriesApiController.cs:91-92]
Key concepts
- Menu group = RestaurantMenu — the backend entity is
RestaurantMenuwithRestaurantMenuCategoriesjoin rows pointing at product categories. "Menu group", "menu category" (API naming) and "menu" are the same thing in different layers. - Single default — setting one group default clears all others atomically (
ExecuteUpdate). The POS falls back to the first menu if none is default. [code: MenuCategoriesApiController.cs:246-251; useOrderCreate.ts:439] - Categories are shared — a category can belong to several menu groups; deleting a group never deletes categories or items.
- Active flag — only active menus are served to the POS (
IsActivefilter inBuildRestaurantMenusAsync); the create path sets new menus active, and the card grid surfaces the badge. [code: OrderMenuController.cs:289-302] - Cache invalidation — menu CRUD invalidates the org's menu + modifier caches so counters see changes without redeploy; SignalR
MenuItemsUpdatedadditionally toasts live counters ("Menu has been updated"). [code: MenuCategoriesApiController.cs:261-262; foodops.counter.app signalRService.ts:305-310]
Common questions
Q: What's the difference between a menu group and a category? A: A category holds items (Espresso, Latte → Hot Drinks); a menu group holds categories (Hot Drinks + Pastries → Breakfast menu). The POS picks a menu group first, then shows its categories.
Q: How many menu groups can I have? A: No limit found in current code. Exactly one can be default at a time.
Q: What happens if I don't set any default menu? A: The POS auto-selects the first menu in the list. [code: useOrderCreate.ts:439]
Q: Can the same category appear in two menus? A: Yes — assignment is a join table; Breakfast and All-Day can both contain Hot Drinks.
Q: Does deleting a menu group delete its items? A: No — only the group and its category links are removed. Items and categories survive. [code: MenuCategoriesApiController.cs:344-368]
Q: Why don't my menu changes show on the POS? A: They should appear automatically (server cache invalidation + the SignalR menu-updated toast). If a counter has been offline, the cached menu refreshes on reconnect. Hard-refresh as a last resort.
Q: Can I schedule a menu by time of day (auto-switch breakfast → lunch)? A: Not found in current code as of 2026-06-11 — there is no time-window field on the menu entity; switching menus is manual via the POS dropdown.
Q: Can different outlets have different menus?
A: Menu groups are organisation-scoped, not outlet-scoped, in the current API (ApplicationOrganisationId filter only). Per-outlet menu assignment was not found in current code as of 2026-06-11 — all outlets see the same menu list. [code: OrderMenuController.cs:289-296]
Q: How do I reorder how categories appear on the POS?
A: Catalog → Menu Order, drag categories — the POS orders categories by SortOrder. Reordering menu groups currently hits a missing endpoint (see walkthrough known gap).
Q: Who can manage menu groups? A: Any authenticated admin-app user behind the standard protected routes; no finer role gate was found on the API controller as of 2026-06-11.
Q: Is the old /menucategories page still usable?
A: No — the legacy haafai.app.foodops back-office (including /menucategories) is retired at the July-2026 launch. The admin app's Catalog → Menu Groups (admin.foodops.io) is the only menu-group surface now.
Q: What's an "Inactive" menu group for? A: A way to park a seasonal menu without deleting it — inactive menus don't appear in the POS Switch Menu list.
Edge cases and known issues
- Menu-group drag-reorder endpoint missing —
MenuOrderPagePUTs/v1/menu-categories/sort-order, which doesn't exist in the sales API at prod HEAD19190b9; expect the menu-group section's reorder to error while category/item reorder works. Flagged for upstream fix. [code: menuGroupService.ts:52-58] - "Select at least one category" — the form blocks empty groups; if every category is removed on edit, save is rejected client-side. [code: MenuGroupFormDialog.tsx:101]
- Default-menu race — two admins setting different defaults concurrently: last write wins; the earlier default is cleared. Single-default invariant holds either way. [code: MenuCategoriesApiController.cs:246-251]
- Hard delete has no undo — unlike most Foodops entities (soft-delete/cancel patterns), menu-group delete physically removes rows including operation logs. Recreate manually if deleted in error.
- Inline "+ New Category" not yet ported — the legacy MVC form had an inline category-create link; the React dialog carries a TODO for it, so create categories first under Catalog → Categories. [code: MenuGroupFormDialog.tsx:182]
Related
Menu Management — Categories, Items, and Pricing — items, variants and pricing inside the categories
Modifiers — modifier groups attached to items
Onboarding — the guided first-menu setup path
Ingredients — raw materials behind the menu items
2026-08-09 — hubits-api-inventory e41f1f1..cffd9cc (feat(categories): add DELETE and reconcile the two on/off fields; + fix(sync) emit categoryId on products feed, price-list tax fix) adds a new DELETE /api/v1/categories/{id} endpoint to CategoriesApiController — previously categories could only be archived/deactivated via the existing ChangeStatus PATCH, not permanently removed. The new endpoint refuses (409, with a dependant count) to delete a category still referenced by products or holding sub-categories, directing the caller to deactivate via PATCH {id}/status instead; an unblocked delete removes the category outright. This is new user-facing category-management behavior, but this entry only cites CategoriesApiController.cs for the sort-order endpoint used by Catalog → Menu Order drag-reorder (CategoriesApiController.cs:524) and doesn't document category create/delete lifecycle at all — so nothing here contradicts the entry's 'Categories are shared... deleting a [menu] group never deletes categories or items' claim, which concerns menu-group deletion via the separate sales-API MenuCategoriesApiController, not this inventory-API category delete. Also in this diff: GetAll's list projection previously omitted IsActive/IsServiceFeeApplication/ShowInOrderingApp (always serialized as the bool default false), and Update/ChangeStatus now keep IsActive and RecordStatusId in sync where they'd drifted — both are category active-flag bookkeeping fixes on the inventory API's category list/edit surface, unrelated to this entry's 'Active flag' claim (which concerns RestaurantMenu/menu-group serving via the sales-API OrderMenuController.BuildRestaurantMenusAsync, a different controller/entity). Note: the new ~75-line Delete method is inserted ahead of the sort-order endpoint, so the entry's CategoriesApiController.cs:524 citation for PUT /api/v1/categories/sort-order has likely drifted downward — a citation refresh, not a factual correction; the sort-order endpoint itself is untouched by this diff. Worth a line under 'Edge cases' or a category-management Q&A if this entry (or a sibling categories entry) is ever expanded to cover category deletion, since attempting to delete an in-use category will now 409 rather than silently succeed or lack a delete option at all. (auto-applied by the truth pipeline; adversarially gated)
2026-08-06 — foodops-admin-app b288f36..2473817 (fix(paging): stop debounced search resetting to page 1 on mount; + report-caching commits unrelated to this entry). Two of this entry's cited files get a paging/search fix: previously the 200ms search-debounce timer unconditionally called resetPage() when it fired, so an operator who paged forward within that 200ms window on Menu Groups or Product Categories got snapped back to page 1 even though their search term hadn't actually changed. Both pages now track the last-committed term in a ref and only reset the page when the debounced term differs from it — a genuine (minor) UX bug fix, not a contradiction of anything currently documented (this entry's walkthrough doesn't describe Menu Groups pagination/search-interaction behavior in the first place; pagination itself was noted but never folded into the body per the 2026-07-18 history entry). Separately, ProductCategoriesPage.tsx is refactored from manual useState/load() to TanStack Query (
useQuery+keepPreviousData), and its existing drag-to-reorder sort-order save now writes the optimistic reorder directly into the query cache (with rollback via the same cache write on failure) instead of local state — same user-visible behavior (immediate reorder, revert on save failure), different plumbing. Neither file is behind any claim in this entry's tracked-claims list (those cite MenuGroupFormDialog, MenuOrderPage, menuGroupService, and the sales/inventory/counter APIs — none touched here), so no CRUD/default/hard-delete/sort-order-gap/cache-invalidation claim is affected. Note: MenuGroupsPage.tsx's debounce-effect edit adds ~9 lines above the card-grid section, so the entry'sMenuGroupsPage.tsx:153-226citation has likely drifted further — a citation refresh, not a factual correction. (auto-applied by the truth pipeline; adversarially gated)2026-07-21 — foodops-admin-app 53d3af8..799c61f (feat(admin): add TanStack Query client + provider (30s staleness contract); feat(admin): write-invalidation bridge — successful writes mark all queries stale) refactors MenuGroupsPage.tsx's data fetching from a manual useState/useEffect + local
load()async function (called on mount, and re-invoked directly by 'Try again', form-save, and delete) touseQuery({ queryKey: MENU_GROUPS_KEY, queryFn: () => menuGroupService.list() }), with the retry/onSaved/onDeleted callbacks now callingqueryClient.invalidateQueries({ queryKey: MENU_GROUPS_KEY })instead of re-runningload()directly. This is the admin app's new client-side query-cache layer (TanStack Query, 30s staleness contract per the commit's own design doc) — a different mechanism from, and layered on top of, the server-side cache invalidation this entry already documents under 'Cache invalidation' (MenuCategoriesApiController's org-level menu/modifier cache plus the SignalR MenuItemsUpdated toast). No documented behavior changes: the card grid still shows the same list, the same 'Couldn't load menu groups' error state with a working 'Try again' button, and a save/delete still refreshes the visible list (now via cache invalidation + automatic refetch rather than a direct re-fetch call) — same net effect for the user. The commit subjects also describe a broader 'write-invalidation bridge — successful writes mark all queries stale', which per its own description marks ALL TanStack queries admin-wide stale on any write, not just menu-groups; that's a wider client-side data-freshness change worth a mention if this entry is ever extended to cover cross-page staleness (e.g. editing a category elsewhere would now also mark the Menu Groups list stale for refetch on next focus), but it does not contradict any CRUD, single-default, sort-order, hard-delete, or existing cache-invalidation claim here. Also worth noting: MenuGroupsPage.tsx gained a new import line ahead of the existing 153-226 card-grid citation, so that line range has likely drifted by ~1 line — a citation refresh, not a factual correction. (auto-applied by the truth pipeline; adversarially gated)2026-07-18 — foodops-admin-app f361a7c..5d0938c (feat: refactor product category management with full-page forms; + UAT batch fixes touching unrelated finance/customer surfaces) adds client-side pagination to the Menu Groups list. MenuGroupsPage.tsx now imports usePagination/usePagedRows and a shared Pagination component: the existing name/category
filteredarray is paged throughusePagedRows(filtered, undefined, page, pageSize, 'menu-groups')and a Pagination control (page, pageSize, totalCount, onPageChange, onPageSizeChange) renders below the card list. A code comment confirms this is client-side only — 'The API has no page/pageSize params yet' — soMenuCategoriesApiControllergained no new list/paging endpoint; this doesn't touch or contradict any CRUD, single-default, sort-order, hard-delete, or cache-invalidation claim in this entry, and the card content/fields (name, Default/Active badge, category count, chips) plus the search-filter behavior in the walkthrough are unchanged — only the number of cards visible at once and a new pager UI are new. The sibling ProductCategoriesPage.tsx (also cited by this entry, for category authoring) goes further: it drops the oldPAGE_SIZE = 100constant and now threadspage/pageSizethrough toproductCategoryService.list(), i.e. genuine server-side paging for categories — again outside anything this entry documents about category creation. Separately, MenuGroupFormDialog's default-toggle off-state class flipsbg-muted→bg-input— a Tailwind color-token swap with the same label/behavior, purely cosmetic. Note: the new imports/hooks land before line 153 in MenuGroupsPage.tsx, so the entry'sMenuGroupsPage.tsx:153-226card-grid citation has likely drifted by roughly 8-10 lines — a citation refresh, not a factual correction. Worth a line in the walkthrough noting a Pagination control now appears under the Menu Groups card grid once verified live. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-07-17 — hubits-api-sales 52a05f6..8ee2cf4 (feat(paging): add opt-in pagination helper; feat(menu-sync) already covered 2026-07-12) touches two of this entry's cited endpoints without contradicting any documented claim. (1) MenuCategoriesApiController.GetAll (backs the admin Menu Groups list, POST/PUT/DELETE for which are documented here) gains optional page/pageSize query params; paging is strictly opt-in — a caller sending neither param (the admin app today, and explicitly 'the POS, which reads this list in full' per the code comment) keeps getting the full unpaged ApiResponse list, so the documented card-grid/list behavior is unaffected. The same method's underlying query, BuildAllAsync, previously had no OrderBy at all (non-deterministic row order) and now sorts by Name then Id — this is a determinism fix for pagination, not the drag-reorder sortOrder this entry documents on the Menu Order page, so no conflict with the 'each sorted by sortOrder' claim. (2) OrderMenuController.GetMenuData — the endpoint behind this entry's documented 'GET /orders/menu-data?menuId=...' POS call — now returns via a new OkWithETag helper instead of a plain Ok(...), adding a strong ETag and conditional-GET support so an unchanged menu can return 304 with no body on repeated fleet-wide POS reads. This is additive server-side caching/performance behavior; the response body shape, category/item contents, and SortOrder-based rendering this entry documents are unchanged. Worth folding into the 'Cache invalidation' key concept as a performance footnote (ETag/304 on menu-data fetch) if this entry is next revised for depth, but no existing claim needs correction. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)
2026-07-17 — hubits-api-inventory a738219..fe5c8f4 (feat(categories): notify counter/ordering menu on category writes, + stock-readiness work) adds role-based [Authorize] gates to CategoriesApiController that were previously absent: a class-level [Authorize(Roles = InventoryRolePolicies.ProductsRead)] now sits alongside [SessionAuth] on every action, plus per-endpoint checks — Products.Create on Create and AddCategoryImage, Products.Edit on Update, ChangeStatus, and the sort-order PUT (the same
PUT /api/v1/categories/sort-orderthis entry cites for the Menu Order page's category/item drag-reorder), and Products.Delete on RemoveCategoryImage. This is new access control on a controller this entry relies on for category reordering — reordering categories on Catalog → Menu Order now requires the Products.Edit role specifically, not just being an authenticated admin-app user. It does not touch or contradict the entry's 'no finer role gate was found on the API controller as of 2026-06-11' claim, which was established against the sibling MenuCategoriesApiController (menu-group CRUD, sales API) — a different controller this diff does not modify. No change to the sort-order route's existence/shape (the 'known gap' — missing sort-order route on MenuCategoriesApiController — is unaffected) or to any CRUD/default-enforcement/delete semantics documented here. Worth a line under 'Edge cases' or a new Q&A if this entry is expanded to cover category-management permissions, since a user without Products.Edit will now see category/item reorder fail with a 403 rather than succeed. (auto-applied by the truth pipeline; adversarially gated)2026-07-14 — foodops-admin-app 79be9d0..f361a7c (feat: refactor product category management with full-page forms) removes the in-page ProductCategoryFormDialog modal from ProductCategoriesPage.tsx and replaces openCreate/openEdit with router navigation to dedicated routes /catalog/categories/new and /catalog/categories/:id/edit — product category create/edit now happens on full pages, not a modal dialog. ProductCategoriesPage.tsx is one of this entry's cited source files, but the entry's body makes no documented claim about the category page's dialog-vs-full-page implementation (only that categories must be created under Catalog → Categories before they can be added to a menu group via MenuGroupFormDialog's multi-select, which is unaffected). No change to Menu Group CRUD, single-default enforcement, sort-order, hard-delete, or cache-invalidation semantics — MenuGroupFormDialog and MenuGroupsPage/MenuOrderPage are untouched by this diff. Worth a line noting the Product Categories authoring UI shape if this entry is ever expanded to cover category management in more depth. (auto-applied by the truth pipeline; adversarially gated)
2026-07-13 — foodops.counter.app 5d29831..2ad5cdf (fix: fall back to default when a category image URL 404s) adds onError handlers + a failedImageIds tracking set to CategoryGrid.tsx and CategorySidebar.tsx: when a category's categoryImageUrl fails to load (e.g. a 404), the component now falls back to the existing placeholder icon (CategoryGrid card view) or initials badge (CategorySidebar) instead of rendering the browser's broken-image glyph. This is a client-side rendering robustness fix only — no change to the API contract, the MenuCategory/RestaurantMenu data model, category sort order, or any CRUD/default-enforcement/delete semantics documented in this entry. Not previously covered here — the entry's 'What changed' section only notes generic April-2026 'design updates' to these files, not image-load-failure handling specifically. Other commits in this diff range (customer identificationNo search, CustomerCombobox display changes) touch unrelated customer-record surface and do not intersect menu-group/category rendering. No body edit required; optionally add a one-line note under 'Edge cases' once/if this is visually confirmed live. (auto-applied by the truth pipeline; adversarially gated)
2026-07-12 — hubits-api-inventory 4fc2a41..a738219 (feat(categories): notify counter/ordering menu on category writes) adds a NotifyMenuChangedAsync helper to CategoriesApiController that, after every category Create, Update, ChangeStatus, AddCategoryImage, RemoveCategoryImage, and UpdateSortOrder call, fires ISalesApiNotificationService.NotifyMenuUpdatedAsync and ICustomerOrderingApiNotificationService.NotifyMenuUpdatedAsync (both best-effort, failures swallowed) — the same cross-service cache-bust/live-update flow the entry already documents for menu-group CRUD via MenuCategoriesApiController and for products. Previously the entry's 'Cache invalidation' key concept and the 'Why don't my menu changes show on the POS?' Q&A described this guarantee only for menu-group writes (name/default/category-membership) and item/product writes; this commit extends the same live-refresh guarantee to plain category edits (rename, status toggle, image add/remove, drag-reorder) — a category rename or deactivation should now also cache-bust and push a live update to the counter and online-ordering menus without a counter reconnect/refresh, closing a gap the entry didn't previously claim either way. Does not touch CategoriesApiController's actual data/CRUD semantics (sort-order route, response shapes) or any sales-API/admin-app file this entry cites — no contradiction to existing claims. Optionally worth folding into the 'Cache invalidation' key concept and the matching Q&A once the sales/ordering notifier behavior is confirmed live. (auto-applied by the truth pipeline; adversarially gated)
2026-07-12 — hubits-api-sales 0854b37..52a05f6 (feat(menu-sync): push MenuItemsUpdated on menu-group & modifier writes) adds an explicit SignalR
MenuItemsUpdatedpush toMenuCategoriesApiController's Create, Update, and Delete actions via a new sharedInvalidateAndNotifyMenuAsync(orgId)helper (newly injectingIHubContext<OrderHub>). Previously these three actions only called_cacheInvalidator.InvalidateMenuAsync/InvalidateModifiersAsyncwith no live push to connected counters. This substantiates rather than contradicts the entry's existing 'Key concepts' claim that menu-group CRUD invalidates caches and additionally fires the SignalR toast on live counters — that claim now has direct first-party evidence in this controller for all three CRUD verbs (Create/Update/Delete), rather than resting on an inferred parallel to the product-edit notification path. No changes to CRUD shape, single-default enforcement, sort-order, or hard-delete semantics. No body edit required; line citationMenuCategoriesApiController.cs:261-262may drift slightly since the two invalidation calls are now a singleInvalidateAndNotifyMenuAsync(orgId)call — worth a citation refresh on the next line-number pass, not a factual correction. (auto-applied by the truth pipeline; adversarially gated)2026-07-06 — hubits-api-sales 3005390..6f242ad (feat(menu-groups): audit deletions and expose history) adds
GET /api/v1/menu-categories/{id}/historytoMenuCategoriesApiController, returning the menu group's operation-log timeline (Created/Edited/StateChanged/etc. with parsed per-field diffs and actor) — this is the backend counterpart to the admin-app History dialog noted on 2026-07-05 (foodops-admin-app@81653c9,menuGroupService.getHistory(id)), which previously had no matching route on the sales API. The same commit also changesDelete: before purgingRestaurantMenuCategoriesand the menu row (and, per the code comment, the row's own operation logs), it now writes a durableAuditLogentry — keyed by EntityType/EntityId strings with no FK to the menu, so it survives the purge — recording the deleting user and the deleted group's name, queryable via the AuditLogs viewer. This confirms rather than contradicts the entry's existing claim that operation logs are purged on delete; it doesn't restore the deleted entity ("hard delete has no undo" still holds), it only adds a forensic trail of the deletion event itself. No CRUD, default-enforcement, sort-order, or delete-semantics changes to menu groups otherwise. The walkthrough's hard-delete step and the History-dialog note could optionally be extended once the AuditLogs viewer and menu-group History dialog are verified live end-to-end. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-07-06 — hubits-api-inventory@a796f71 (feat(catalog): complete operation-log coverage for categories & brands) adds
GET /api/v1/categories/{id}/historytoCategoriesApiController, returning the category's full audit timeline (Created/Edited/StateChanged operation logs with parsed per-field diffs and actor), explicitly built to mirror the Items History tab. This is the backend counterpart to the admin-app History dialog noted on 2026-07-05 (foodops-admin-app@81653c9,productCategoryService.getHistory(id)) — confirms that action is now backed by a real endpoint rather than a stub. The commit also reclassifiesChangeStatuscategory-status changes fromEditedtoStateChangedop-log type and addsEditedlog entries for category image add/remove; none of this alters documented CRUD, sort-order, or delete semantics for menu groups/categories — the existingPUT /api/v1/categories/sort-orderendpoint is unchanged. No edits to entry body required; the walkthrough's card-action list could optionally be extended to confirm the History dialog is now fully wired end-to-end once verified live. (auto-applied by the truth pipeline; adversarially gated)2026-07-05 — foodops-admin-app@81653c9 (feat(catalog): add History dialog for categories, brands & menu groups) adds a History action to the Menu Groups card grid (and to Product Categories) that opens an OperationLogHistoryDialog backed by
menuGroupService.getHistory(id)/productCategoryService.getHistory(id), surfacing each entity's operation log in the admin UI alongside the existing edit/delete actions. This is additive — it doesn't change CRUD, default-enforcement, or delete semantics already documented — but the walkthrough's card-action list (pencil/trash) and card description should be updated to mention the new History icon once the dialog's rendered content is verified live. (auto-applied by the truth pipeline; adversarially gated)- 2026-06-11 — codebase: promoted stub → draft against
foodops-admin-app@77ec95f,hubits-api-sales@5d917e5,foodops.counter.app@4cbcfd7,haafai.app.foodops@f06cdd9. Full CRUD walkthrough written from the admin-app card grid + form dialog source and the/api/v1/menu-categoriescontroller: single-default enforcement, "Select at least one category" validation, hard delete semantics, cache invalidation, POS default-menu auto-select and Switch Menu dropdown. NEW FINDING (verify-and-note): the Menu Order page's menu-group drag-reorder targetsPUT /v1/menu-categories/sort-order, which is absent from the sales API at HEAD — likely broken; category/item reorder endpoints exist (inventory API). Time-of-day menus and per-outlet menu assignment verified ABSENT. Confidencemedium; UI pending Playwright walk.
Warning: Code-verified 2026-05-04 · codebase only (auth.json stale) Verified_by:
codebase· Last verified: 2026-05-04 Source-repo globs curated against real haafai paths (was previously the brokensrc/menu/**fallback). Authoring backend (MenuCategoriesController.cs+Views/MenuCategories/**inhaafai.app.foodops) had no commits since 2026-04-15 — controller/view layer unchanged. POS counter-side rendering (CategorySidebar,MenuContentArea,MenuSelector,ProductGridinfoodops.counter.app) saw a design-update pass 2026-04-21 → 2026-04-24 (73b53ef,154ccfc,715180d,ffa9b05). Visual changes captured in the "What changed" section; UI screenshots and exact label re-walk pending auth.json refresh. Verified againstfoodops.counter.app:154ccfcandhaafai.app.foodops:4fdfac7.
2026-05-04 — codebase verification at haafai/foodops.counter.app:154ccfc, haafai/haafai.app.foodops:4fdfac7; UI re-walk pending auth refresh
2026-06-03 — source_repos re-pointed for the 2026-06-03 backend microservice split. ADDED hubits-api-sales (menu API — OrderMenuController.cs + MenuCategoriesApiController.cs, serving menu-group/category retrieval to the counter) pinned to main HEAD 5274891, and foodops-admin-app (menu-group SETUP backoffice — MenuGroupsPage + MenuOrderPage + ProductCategoriesPage + MenuGroupFormDialog + DeleteMenuGroupDialog) pinned to main HEAD f3ae3e9. All added paths verified present on main via gh api. Existing foodops.counter.app and haafai.app.foodops globs unchanged (all still resolve on prod branches). No fallback/broken globs found. Stub status retained.
2026-06-03 — content re-verification against hubits-api-sales@5274891 (incl. multi-GST bc04a77), foodops-admin-app@f3ae3e9, foodops.counter.app@e002374. Multi-GST (bc04a77, 2026-06-02) added per-component Taxes[] only at the product/variant level (MenuProductDto/MenuProductVariantDto) — MenuCategoryDataDto (the menu-group/category container) is UNCHANGED and no category/menu-group files were touched by the commit. Menu-group behavior is therefore unaffected; no body change required. Stub status retained. NOTE: counter source_repos still globs src/components/orders/CategorySidebar.tsx etc. on foodops.counter.app — those paths still resolve on main. (Tax modeling lives on items; see items.md "What changed (2026-06-03)" for the multi-GST plumbing details.)
2026-06-29 — NEW-stack re-verification for the July-10 launch (legacy haafai.app.foodops monolith RETIRED). Re-pinned to prod HEADs: foodops.counter.app@32669a9, hubits-api-sales@19190b9, hubits-api-inventory@04df7a9 (ADDED — category sort-order), foodops-admin-app@f6ffd0b. Dropped retired haafai/haafai.app.foodops (MenuCategories + Onboarding controllers/views). CORRECTIONS: reframed the legacy /menucategories back-office references as retired (How-to-access, the "old page still usable?" Q&A); added a launch banner. RE-VERIFIED the "menu-group drag-reorder endpoint missing" known gap — STILL PRESENT on prod: MenuCategoriesApiController @ 19190b9 exposes only GET / GET{id} / GET product-categories / POST / PUT{id} / DELETE{id}, no sort-order route, so PUT /v1/menu-categories/sort-order from MenuOrderPage 404s (category/item reorder via inventory API unaffected). Single-default enforcement, hard-delete, cache-invalidation, POS default-menu auto-select facts retained. Human-curated sections preserved. NOT re-walked live (code-only).
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.