15 · Floor map · KDS · GPE Signage

Visual seating/floor design, kitchen tickets, and venue display playlists — plus the product goal: relay a customer’s seat on prep-area screens (one or many), driven by the same layout the POS used.

Floor → POS → KDS seat chips: works Floor map on venue displays: not built yet Product name: GPE Signage (not third-party brand names)
Naming Digital displays in this platform are GPE Signage (or “venue displays / digital signage”). Do not use third-party product names (e.g. nSign) in product, marketing, or docs.

Product vision (your intent)

  1. Draw the room — theater, restaurant, patio — in the visual floor mapper (stable seat/table IDs).
  2. Sell at POS — staff (or kiosk) attach that seat/table to the order.
  3. Prep sees where it goes — KDS already shows text chips; the fuller goal is a visual mini floor map on one or more prep/expo screens (and optionally lobby screens), highlighting the seat and order state.
  4. GPE Signage carries the screen — same player/display registry that already runs ads, with a new live board / map mode fed by KDS (not only static ad playlists).

That last hop (live map/board on Signage displays) is the missing product slice. Floor design + POS + kitchen tickets already share IDs for it.

Who is at which seat? (this is where it gets deep)

A prep screen cannot highlight a seat unless the order carries a seat. A seat only means something for CRM/rewards if the order also carries a customer. Those are two independent attachments that can arrive in different ways.

flowchart TB
  subgraph WHERE["WHERE — seat on the order"]
    W1[Staff taps seat on floor map
Modern POS · seating visualizer] W2[Staff types table/seat label] W3[Guest picks seat at kiosk / storefront
future] W4[Admission / show ticket already has seat
future link] end subgraph WHO["WHO — customer on the order"] H1[Staff attaches customer at POS
search CRM] H2[Member scans / looks up loyalty ID] H3[Logged-in storefront / app account] H4[Guest checkout — no member] end WHERE --> CART[POS cart metadata + customerId] WHO --> CART CART --> SALE[Finalize → Invoice + kitchen_tickets] SALE --> KDS[Prep sees seat] SALE --> LOY[Loyalty earn / rewards if customer linked] SALE --> MAP[Future live map on GPE Signage]

Paths that already work in code

PathWhat staff/customer doesToday
Server-attached seat On Modern POS with seating visualizer, pick seat on map → cart metadata Built (not on Mobile POS yet)
Server-attached customer Search/attach customer on cart · posApi.attachCustomer Built Modern + Mobile
Loyalty tender / points Redeem against cart when customer/loyalty account exists Built (Modern UI; loyalty program admin)
Anonymous walk-up No customer, seat optional Works — seat alone still helps kitchen; no rewards trail

Paths that are product design / future glue

PathIdeaWhat must exist
Member-required seat map Only members can reserve/show seat on board Policy flag + enforce customer on cart before seat (or before finalize)
Admission ticket → seat They bought Row F Seat 12 under their account earlier; concessions auto-tag that seat Event/admission inventory linked to same seat IDs + customer; not the same as service-desk “tickets” module today
Scan membership → last seat Scan rewards barcode; system recalls “currently seated in …” for this visit Visit/session object: customer + layout + seat + time window
Self-serve kiosk seat pick Guest taps seat then orders Kiosk UI + floor map component + optional login/member gate
Important split Platform service tickets (salon/repair queue) and admission/show tickets (movie seat sold in advance) are different product concepts. Today’s tickets module is service-desk oriented. Theater admission → seat is a vertical that reuses floor IDs + customers + invoicing, not necessarily the existing ticket-workflow engine as-is.

How deep the graph actually goes

  • Floor mapper — geometry + stable seat IDs
  • POS / Mobile POS — attach seat + customer at order time
  • Customers CRM — who they are
  • Loyalty / rewards / tiers — earn/redeem when customer is on the sale
  • Invoicing — money spine; sale is source of truth
  • KDS — prep execution + seat chips (and later mini-map)
  • GPE Signage — multi prep/lobby screens (playlist today; live map later)
  • Optional: visits / admissions — pre-known seat before concessions order

None of that requires every guest to be a member. Membership deepens personalization and rewards; seat-on-order is enough for kitchen routing; customer-on-order is enough for rewards. Combining both is how theater “knows” it’s the same person in F12 ordering nachos.

Visual floor mapper (exists)

PieceWhereRole
Layout designer /admin/seating-layouts · SeatingLayoutManager.jsx (~1k lines) Grid editor: rows/cols, seats, aisles, stage/lobby toggles, labels
Room templates In designer Small/medium/large cinema, theater, restaurant-sized grids
API /api/seating-layouts CRUD Persists layoutData JSON (grid, rows, cols, cellSize, metadata)
POS picker PosSeatingPicker.jsx · Modern POS Tap a seat on the same grid → writes cart metadata
Device flag Device settings · seatingVisualizerEnabled Turns on visualizer for that POS device

What gets stored on the cart / ticket

Stable identities from the designer — this is the join key for every downstream screen.

FieldMeaning
seatIdStable cell/seat id from layout grid
tableIdTable identity when used
seatNumber / tableNumberHuman labels for chips (“Seat 12”, “Table 5”)
Already on kitchen tickets Finalize / send-to-kitchen copies these onto kitchen_tickets. KDS renders them as text chips. The layout JSON itself is not yet re-rendered on KDS or Signage.

Three systems (as-built vs intended)

Floor mapperKDSGPE Signage
Job Define room + stable seat IDs Cook/expo work tickets Drive TVs (ads today; live boards later)
Primary UI /admin/seating-layouts /pos/kitchen /admin/signage + public player
Data seating layouts + layoutData kitchen_tickets SignageFolders / Ads / Displays
Sees seats today? Designs them Text chips only No
Intended for prep screens Source of map geometry Source of order state Player that can show map + state on one or many displays

KDS today (summary)

  • UI: /pos/kitchen — lanes FIRE / PREP / FUTURE / DONE
  • Poll GET /api/kds/orders ~3s (no WebSocket)
  • Bump / recall / print; station filter ?station=
  • Feature gate: plan module kds
  • Tickets from POS finalize + Mobile “send to kitchen”
  • Admin routing: /admin/kitchen-displays · /api/admin/kds

Detail tables for routes/schema remain valid from the prior KDS write-up; focus here is floor + multi-screen vision.

GPE Signage today (summary)

  • Product name: GPE Signage / venue digital displays
  • Admin: folders, ads (image/video/URL/HTML), displays with slugs
  • Player: /:tenantSlug/signage/:displaySlug
  • Public playlist API + bridge signageUpdate + optional ?reloadSec=
  • Media = hosted URLs (not the platform barcode upload store)
Today’s Signage is playlist-only There is no display “mode” for live kitchen tickets or a floor map overlay. Prep-area TVs can only show whatever ads you put in the playlist unless we build live modes.

As-built data path

flowchart TD
  FL[Floor mapper
layoutData + seat IDs] POS[Modern POS
PosSeatingPicker] META[Cart metadata
seatId tableId numbers] KT[(kitchen_tickets)] KDS[KDS text chips] SIG[GPE Signage
ad playlist only] FL --> POS POS --> META META -->|finalize / send-to-kitchen| KT KT --> KDS KT -.->|no feed| SIG ADS[Admin ads] --> SIG

Target architecture (prep screens · multi-display)

Goal: any prep/expo/lobby display can show a live map (or ticket board) for seats that have open food, without re-drawing the floor in three products.

flowchart TB
  subgraph SOURCE["Single sources of truth"]
    FL[Floor layout JSON
geometry + seat IDs] KT[kitchen_tickets
order state + seatId] end subgraph FEED["New: status feed"] API["GET /api/kds/board or /api/signage/live/…
layoutId + open tickets + lane"] end subgraph SCREENS["One or many displays"] KDSUI[KDS web · optional mini-map] PREP1[GPE Signage display A
mode=live-map · grill] PREP2[GPE Signage display B
mode=live-map · expo] LOBBY[Optional lobby
mode=order-ready] end FL --> API KT --> API API --> KDSUI API --> PREP1 API --> PREP2 API --> LOBBY

Recommended design choices

ChoiceRationale
Reuse seating layout IDs Already on tickets; never invent a second seat key space
Reuse GPE Signage Displays Already multi-screen (many slugs), bridge refresh, player URL pattern — add a display_mode or ad type: live_map / order_board vs playlist
Filter by station / lane Grill TV only shows grill tickets; expo shows all FIRE/DONE as needed — mirrors KDS ?station=
Read-only on prep TVs Bump/recall stay on staff KDS (or PIN-gated); map screens only highlight
Poll first, WS later Match current KDS ~3s poll for v1; push via bridge when ready

Build list (to match the vision)

#WorkDepends on
1 Board/status API — layout snapshot + open tickets with seatId/lane/station Existing tickets + seating layouts
2 Shared FloorMapView component — render layoutData.grid + highlight seats by ticket state layoutData schema (exists)
3 Optional mini-map on KDS — same component, staff screen #1–2
4 GPE Signage display mode — live_map / order_board on a display slug; player branch #1–2 · SignageDisplays schema extension
5 Multi-display config — per-display station filter, layout pick, orientation #4
6 Mobile POS seating — parity with Modern so concessions phones can attach seats PosSeatingPicker reuse
7 Bridge push for live board refresh (optional polish) #4 · existing signageUpdate path

File anchors

ConcernPath
Floor designerfrontend/src/pages/admin/sections/seating/SeatingLayoutManager.jsx
Seating APIbackend/routes/seatingLayouts.routes.js · /api/seating-layouts
POS seat pickcomponents/POS/PosSeatingPicker.jsx · ModernPOS seatingVisualizer
Ticket seat fieldsposTenantQueries.ensureKitchenTicketsTable · pos.routes insert
KDS UI / APIKitchenDisplay.jsx · kds.routes.js
GPE SignageSignageAdsCatalog.jsx · SignagePlayer.jsx · signage.routes.js
Bridge display refreshbridge/server.js signage-relay · signageUpdate

Related DocHub