Remove the transitional layer added in commits 1+2:
models.py
- Drop Category, Subcategory, CreateCategory, CreateSubcategory.
crud.py
- Drop create_category / update_category / get_category /
get_categories / delete_category and the subcategory variants
along with the _node_row_to_category / _node_row_to_subcategory
helpers. Tree state is owned exclusively by menu_node CRUD now.
views_api.py
- Remove old endpoints:
GET /api/v1/restaurants/{id}/categories
POST /api/v1/categories
DELETE /api/v1/categories/{id}
GET /api/v1/categories/{id}/subcategories
POST /api/v1/subcategories
DELETE /api/v1/subcategories/{id}
Hits return 404 now.
- GET /api/v1/restaurants/{id}/menu loses the synthetic
'categories' projection. Response is {restaurant, tree, items}.
static/js/api.js
- Drop listCategories / createCategory / deleteCategory and the
subcategory wrappers.
The CMS menu builder is broken between this commit and commit 4.
The plan acknowledged this trade-off: keeping commits revertible
beats the cost of an unshipped UI page rendering a stale empty
sidebar for one commit's lifetime.
views_api.py:
- New endpoints (admin-key-gated, ownership-checked):
* GET /api/v1/restaurants/{id}/menu_nodes flat list of nodes
* GET /api/v1/menu_nodes/{id} single node
* POST /api/v1/menu_nodes create
* PUT /api/v1/menu_nodes/{id} edit name / desc /
sort_order /
image_url
* PUT /api/v1/menu_nodes/{id}/move body
{new_parent_id}
* DELETE /api/v1/menu_nodes/{id}?cascade=true|false
- ValueError from CRUD (depth, cycle, has-children-without-cascade)
surfaces as 400 (creates / moves) or 409 (delete blocked).
- GET /api/v1/restaurants/{id}/menu now returns three views in
one round trip:
tree: hydrated tree (root nodes -> children + items)
items: flat enriched list (modifiers + availability)
categories: transitional projection of depth-0 nodes with
their immediate items, in the legacy shape — kept
for one commit's lifetime so the existing CMS
keeps rendering. Drops in commit 3.
static/js/api.js:
- listMenuNodes / getMenuNode / createMenuNode / updateMenuNode /
moveMenuNode / deleteMenuNode added.
- Old category/subcategory methods marked transitional in
comments (drop in commit 3).
No JS / template churn — the CMS still reads from menu.categories
which is now produced from menu_nodes via the synthetic projection.
Commit 4 replaces the CMS with q-tree.
LNbits convention: extends base.html, declares window.app =
Vue.createApp({mixins: [windowMixin], data, methods, created}); the
LNbits init-app.js loads after extension scripts and finishes the
mount with app.use(Quasar) + app.mount('#vue').
Pages
- index.html restaurant list / dashboard with create dialog;
scoped to the logged-in user's wallets.
- menu.html category sidebar + items grid; full item dialog
with price/currency/images/dietary/allergens/
ingredients/calories/stock/availability/featured.
Modifier groups managed in a separate dialog
with required|optional + one|many semantics.
- orders.html filterable q-table with status colors and inline
state-machine actions (accept/ready/complete/
cancel). Polls every 8s.
- kds.html kitchen display: card-per-order, items + selected
modifiers + notes, age-based color escalation
(>5min orange, >15min red), polls every 5s. The
poll loop is a stand-in until SSE/Nostr push
lands.
- settings.html restaurant profile editor + delete + per-instance
ext settings panel (Nostr publish toggle, auto-
accept, invoice expiry).
Static
- js/api.js single REST client (LNbits.api.request wrapper)
used by all pages.
- js/index.js dashboard logic.
- js/menu.js menu CRUD.
- js/orders.js order monitor.
- js/kds.js kitchen display.
- js/settings.js settings persistence.
Customer kiosk UI lives in ~/dev/webapp; this extension only ships
the operator console.