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.