restaurant/docs/architecture.md
Padreug 42a8b08a5b docs: Obsidian-style vault under docs/
Add a navigable Obsidian vault as the project's first-class
technical documentation. Notes cross-reference with [[wikilinks]];
docs/index.md is the Map of Content.

New notes:
  index.md             MOC, entry point
  architecture.md      what the extension owns vs what lives outside
  data-model.md        entity-by-entity schema reference
  menu-tree.md         the arbitrary-depth tree concept
  order-flow.md        state machine + invoice listener + print
  nostr-layer.md       kinds 0/30402/5/1059, signing, t-tags
  api-reference.md     endpoint catalog by audience
  cms.md               Vue 3 + Quasar 2 UMD conventions, q-tree
  webapp-integration.md  multi-restaurant cart pattern + atomicity
  glossary.md          domain terms

Existing notes (kept as-is):
  adr-0001-menu-tree.md  the storage choice rationale
  design-conversation.md trimmed transcript

README.md adds a Documentation section pointing at docs/index.md
with the headline note list. Each note links to ~3-5 others; the
vault forms a connected graph.

A project-level memory rule (saved outside the repo) commits us to
keeping these docs in sync as the code evolves: any commit that
materially changes schema, API, order flow, Nostr surface, CMS
conventions, or webapp integration must update the relevant note(s)
in the same commit.
2026-05-09 07:11:06 +02:00

4.2 KiB

Architecture

The restaurant extension is the operator's CMS for one or many restaurants on a single LNbits account. Customer-facing UIs (kiosks, mobile apps, the AIO webapp) live outside the extension and consume it over REST + Nostr.

What this extension owns

  • Restaurant profile rows and per-restaurant Nostr identity.
  • The menu-tree (menu_nodes + menu_items + modifier_groups + modifiers + availability_windows).
  • The order-flow (orders, order_items, print_jobs).
  • Publishing the nostr-layer as NIP-99 listings.
  • The cms under /restaurant/... (Jinja + Quasar 2 UMD).
  • A REST api-reference under /restaurant/api/v1/....

What lives outside the extension

Concern Where
Customer kiosk / mobile / web ~/dev/webapp (webapp-integration)
Multi-restaurant aggregation (festivals, food courts, collective spaces) NIP-51 lists, curated externally
Lightning wallet, payment routing, user auth LNbits core
Nostr relay connection nostrclient extension
Thermal printer printer-pi (subscribes to a webhook or Nostr event)

High-level topology

                         LNbits instance
                ┌────────────────────────────────┐
                │  Restaurant ext                │
                │  ├── REST    /restaurant/api/v1│
                │  ├── CMS     /restaurant/...   │
                │  ├── Nostr publisher           │──────┐
                │  └── Invoice listener          │      │
                │       (settle, decrement,      │      │
                │        queue print)            │      │
                └─────────┬──────────────────────┘      │
                          │                             ▼
                          │              ┌─────────────────────────┐
                          │              │ nostrclient ext         │──→ relays
                          │              └─────────────────────────┘
                          ▼
                  ┌──────────────┐         ┌─────────────────────┐
                  │ printer-pi   │ ◀──────│ webapp / AIO        │
                  │ (subscribes) │         │ (customer, multi-   │
                  └──────────────┘         │  restaurant cart)   │
                                           └─────────────────────┘

Lifecycle

__init__.py registers three permanent tasks on extension start (create_permanent_unique_task):

  1. Invoice listenertasks.wait_for_paid_invoices consumes LNbits' global payment queue, filters on payment.extra.tag == "restaurant", and dispatches to order-flow.
  2. NostrClient bootstrapnostr.nostr_client.NostrClient connects to the nostrclient extension's internal WebSocket after a 10s grace.
  3. Nostr syncnostr_sync.wait_for_nostr_events subscribes to the relevant filters once the client is up. NIP-17 unwrap is stubbed.

restaurant_stop cancels the three tasks and closes the WebSocket.

If nostrclient isn't enabled, the publisher / sync no-op gracefully and the extension still works — it just operates as REST-only without the nostr-layer.

Boundaries we keep

The extension only ever knows about its own restaurant's data. There is no global "festival" or "marketplace" entity stored anywhere in restaurant.* tables. Cross-restaurant grouping is the customer webapp's concern; see webapp-integration.

See also