LunarFront Platform

Frontend Strategy — Multiple UI Surfaces

Version 1.0  |  Draft



# 1. Overview

LunarFront serves different users in different contexts — a store owner at their desk, a cashier at the counter, a staff member receiving stock in the back room, and a salesperson at a trade show. One UI cannot serve all of these well.

The platform uses a single backend API with multiple purpose-built frontends. Each frontend is a separate package in the monorepo, shares the same `@lunarfront/shared` schemas and types, and calls the same REST API. The backend doesn't know or care which frontend is making the request.



# 2. Frontend Packages

## 2.1 Admin UI — `packages/admin`

**Status:** Active development (current)

| Attribute | Detail |
|-----------|--------|
| Form factor | Desktop / laptop, large tablet |
| Stack | React, Vite, TanStack Router, TanStack Query, shadcn/ui |
| Users | Store owner, manager, back-office staff |
| Access | Web browser |

**Purpose:** Full management interface. Everything that isn't a real-time transaction happens here — accounts, members, inventory configuration, RBAC, roles, reports, settings, lesson scheduling, repair management, vault.

**Design priorities:**
- Dense information display (data tables, detail pages)
- Keyboard-friendly (tab navigation, search)
- Multi-column layouts, sidebars, dialogs
- Not optimized for touch or small screens — that's intentional

## 2.2 POS UI — `packages/pos`

**Status:** Planned

| Attribute | Detail |
|-----------|--------|
| Form factor | Counter-mounted tablet (10"+) or touchscreen monitor |
| Stack | React, Vite (same toolchain as admin) |
| Users | Cashier, sales associate |
| Access | Web browser (full-screen / kiosk mode) |

**Purpose:** Point-of-sale for in-store transactions. Staff stares at this 8 hours a day. Every interaction must be fast and require minimal taps.

**Design priorities:**
- Always-on, single-screen (no page navigation)
- Large touch targets (44px+ minimum)
- Fast product search (keyboard wedge barcode scanner + text search)
- Cart management (add, remove, adjust quantity, apply discount)
- Payment flow (cash, card via terminal, split payment)
- Cash drawer integration
- Receipt printing (thermal printer via browser print or ESC/POS)
- Session management (clock in/out, cash drawer open/close, X/Z reports)
- Offline resilience — queue transactions if network drops, sync when back

**PIN unlock:**

The POS terminal stays authenticated but locks between use. Staff unlock with a 4-6 digit PIN instead of email/password. The PIN maps to their real user account — every action (sale, void, drawer open) is attributed to the person who unlocked.

- Each user sets a numeric PIN from their profile or admin assigns one
- PIN is stored as a bcrypt hash on the user record (separate from password)
- POS lock screen shows a numeric keypad — tap PIN, unlock, start selling
- Auto-locks after configurable idle timeout (default: 2 minutes)
- Manual lock button always visible
- Shift change: current user walks away, screen locks, next user enters their PIN
- Wrong PIN: brief delay, max 5 attempts then require full login
- PIN unlock issues a short-lived JWT (or extends the existing session) scoped to POS permissions
- Backend: `POST /v1/auth/pin-login` — accepts `{ pin }`, returns token with same user identity and permissions
- PIN is optional — users without a PIN set must use full email/password login on the POS

This gives full audit trail (who did what) without the friction of typing credentials hundreds of times a day.

**Discounts & price overrides:**
- Line item discount (percentage or flat dollar amount)
- Whole-cart discount (e.g. 10% off everything)
- Price override with reason code (item rings up wrong, manual correction)
- Manager override: cashier attempts a discount or void above their permission level, manager punches their PIN to approve without taking over the session. Logged as "approved by [manager] for [cashier]"
- Configurable discount limits per role (e.g. sales associate can give up to 10%, manager up to 25%, admin unlimited)

**Parked carts / layaway:**
- Park a cart — customer wants to keep shopping or come back later
- Multiple parked carts, each tagged with customer name or account
- Retrieve a parked cart to resume the sale
- Layaway: park with a deposit, schedule remaining payments
- Auto-expire parked carts after configurable period (default: 7 days)

**Gift cards & store credit:**
- Sell a gift card (physical or digital, generates a unique code)
- Redeem gift card as payment method (scan or type code)
- Check balance
- Issue store credit (returns, adjustments) — tied to an account
- Store credit appears as a payment option when account is linked to the sale

**Returns:**
- With receipt: lookup transaction by receipt number or barcode, select items, refund to original payment method
- Without receipt: store credit only, capped amount (configurable), requires manager override
- Exchange: return + new sale in one transaction
- Restocking fee support (percentage or flat, configurable per category)
- Returned items: prompt for condition (resellable, damaged, defective) — updates inventory accordingly

**End of day / cash management:**
- **Open session:** cashier counts starting cash, enters amount, system records
- **Cash drops:** mid-shift safe drops, logged with amount and time
- **Close session:** two modes:
  - *Counted close:* cashier counts the drawer, enters totals by denomination, system calculates over/short
  - *Blind close:* cashier counts and enters total without seeing expected amount — prevents fudging
- **Z report:** end-of-day summary — gross sales, net sales, tax collected, payment method breakdown (cash, card, gift card, store credit), refunds, discounts given, over/short
- **X report:** mid-day snapshot (same data as Z but doesn't close the session)
- All reports printable on receipt printer

**Training mode:**
- Toggle per POS session — transactions don't hit real inventory or accounting
- Visually distinct: large "TRAINING MODE" banner across the screen, different background color
- Manager enables/disables via their PIN
- Training transactions are logged separately for review but don't affect reports or stock

**Customer-facing display:**
- Second screen (or portion of a split screen) showing the customer what's being rung up
- Line items, quantities, prices, running total
- Shows payment status ("Insert card", "Approved", "Change due: $4.25")
- Some jurisdictions legally require itemized display during transaction
- Configurable: on/off, which screen, store branding/logo

**Quick keys / favorites:**
- Configurable grid of frequently sold items (strings, picks, reeds, cables, lesson payments)
- Admin configures the grid layout (drag and drop in admin UI)
- Supports categories/tabs (e.g. "Accessories", "Lessons", "Repairs")
- Faster than searching — one tap to add to cart
- Can also map to common actions (open drawer, apply house discount, gift card sale)

**Receipts:**
- Thermal printer via ESC/POS (USB or network)
- Browser print fallback with receipt-formatted CSS
- Email receipt option (if customer has account with email)
- Text/SMS receipt (future, via email system)
- Configurable header/footer (store name, address, return policy, social media)
- Barcode/QR on receipt for easy return lookup

**Key screens:**
1. **Lock screen** — numeric keypad, current time, store name
2. **Sale screen** — product search (left), quick keys (top), cart (right), totals + pay button (bottom)
3. **Payment screen** — amount due, payment method selection, card terminal status, change calculation, tip (optional)
4. **Return/exchange screen** — lookup original transaction, select items, refund method, condition prompt
5. **Parked carts** — list of held transactions, tap to resume
6. **Drawer management** — open session, cash drops, close session with denomination counting
7. **Reports** — X report, Z report, transaction history for current session

**Hardware integration:**
- Barcode scanner: keyboard wedge (types into focused input, no special API)
- Card reader: local network or USB terminal via Stripe Terminal SDK or Global Payments SDK
- Cash drawer: triggered via receipt printer (kick pulse) or USB relay
- Receipt printer: ESC/POS over USB or network, or browser `window.print()` with receipt-formatted CSS
- Customer display: secondary screen via `window.open()` or dedicated display API
- Scale (future): for items sold by weight

## 2.3 Floor App — `packages/floor`

**Status:** Planned

| Attribute | Detail |
|-----------|--------|
| Form factor | Phone or small tablet |
| Stack | React Native (Expo) or PWA |
| Users | Sales staff on floor, inventory staff, trade show sales |
| Access | Native app (iOS/Android) or PWA in Safari/Chrome |

**Purpose:** Two modes in one mobile app — inventory management and mobile sales. Both are walk-around-the-store (or walk-around-a-trade-show) workflows with scanning, big inputs, and minimal typing.

**Design priorities:**
- Phone-first layout (single column, stacked)
- Camera barcode scanning (no external scanner needed)
- Large buttons, minimal text input
- Works on cellular (trade shows) and store WiFi
- Offline support for inventory counts (sync when connected)

### Inventory Mode

**Purpose:** Receiving, cycle counts, transfers, shelf checks.

**Key screens:**
1. **Receive stock** — scan items, enter quantities, confirm receipt against purchase order
2. **Cycle count** — scan location/shelf, scan items, enter counts, submit discrepancies
3. **Quick lookup** — scan barcode, see product details, stock levels across locations, price history
4. **Transfer** — scan items to move between locations
5. **Condition check** — scan item, update condition (new, used, damaged), add notes/photo

### Mobile POS Mode

**Purpose:** Sales at trade shows, on the shop floor, off-site events.

**Key screens:**
1. **Quick sale** — search/scan products, add to cart, take payment
2. **Payment** — Bluetooth card reader integration, or manual card entry
3. **Customer lookup** — search by name/phone/account number for account-linked sales
4. **Receipt** — email or text receipt (no printer at a trade show)

### Build Strategy

**Phase 1 — PWA:**
- Build as a web app with mobile-optimized UI
- "Add to Home Screen" for app-like experience
- Camera scanning via `navigator.mediaDevices` + barcode detection library
- Local network card reader for payments
- No App Store, no developer cert, works on any device

**Phase 2 — Native (if needed):**
- React Native with Expo
- Reuses `@lunarfront/shared` schemas and business logic
- Native Bluetooth for card reader pairing
- Native camera for faster barcode scanning
- Requires Apple Developer ($99/year) and Google Play ($25 one-time)
- Only worth it if PWA limitations become blockers (Bluetooth, offline, performance)



# 3. Shared Infrastructure

All frontends share:

| Layer | Package | What it provides |
|-------|---------|------------------|
| Validation | `@lunarfront/shared` | Zod schemas — same validation client and server |
| Types | `@lunarfront/shared` | TypeScript interfaces for all domain entities |
| Business logic | `@lunarfront/shared` | Formatters, calculators, state normalization |
| API | Backend REST | Same endpoints, same auth, same response format |
| Auth | JWT | Same token works across all frontends |
| Permissions | RBAC | Same permission checks — POS user doesn't need admin access |

This means:
- A bug fix in a Zod schema fixes validation everywhere
- A new API endpoint is immediately available to all frontends
- No data format translation between frontends
- One test suite covers the API regardless of which frontend calls it



# 4. Permission Mapping

Different frontends surface different permissions:

| Permission | Admin | POS | Floor |
|------------|-------|-----|-------|
| `accounts.view/edit/admin` | Yes | View only (customer lookup) | View only |
| `inventory.view/edit/admin` | Yes | View only (product search) | Full access |
| `pos.view/edit/admin` | Reports only | Full access | Mobile POS mode |
| `rentals.*` | Yes | Process returns | No |
| `lessons.*` | Yes | No | No |
| `repairs.*` | Yes | Intake only | No |
| `vault.*` | Yes | No | No |
| `users.*` | Yes | No | No |

The backend enforces permissions regardless — the frontend just decides what to show.



# 5. Offline Strategy

Offline support is critical for POS (network drops) and Floor (dead zones in warehouse).

**POS offline:**
- Cache product catalog in IndexedDB (refresh periodically)
- Queue transactions locally when offline
- Process card payments when back online (or show "cash only" mode)
- Sync queue when connection restores
- Visual indicator: "Offline — transactions will sync when connected"

**Floor offline (inventory):**
- Cache current inventory counts
- Store count entries locally
- Sync on reconnect
- Conflict resolution: last-write-wins with audit log of discrepancies

**Admin:**
- No offline support needed. Back-office work requires connectivity.



# 6. Deployment

| Frontend | Hosting | Notes |
|----------|---------|-------|
| Admin | Same server as backend (Caddy/nginx serves static files) | Single origin, no CORS |
| POS | Same server or dedicated POS terminal | Kiosk mode browser, auto-launch on boot |
| Floor | PWA from same server, or App Store | Phone/tablet accesses over store WiFi or cellular |

For on-prem: all frontends are served from the same LunarFront server. One box, one URL, multiple apps at different paths (`/admin`, `/pos`, `/floor`).



# 7. Implementation Order

1. **Admin UI** — current, continue until core management is complete
2. **POS UI** — next major effort after admin is stable. This is the revenue-critical surface.
3. **Floor App (Inventory mode)** — PWA first. Enables stock receiving and cycle counts.
4. **Floor App (Mobile POS mode)** — add-on to floor app. Trade show / floor sales.
5. **Floor App (Native)** — only if PWA hits limitations. Bluetooth reader, performance, offline.

Each frontend is independently deployable. Stores can run just Admin + POS if they don't need mobile inventory. Modules control which features are available, not which frontends are installed.
