738 lines
41 KiB
Markdown
738 lines
41 KiB
Markdown
|
|
# Smart Intake Workflow — Screen Specification
|
|||
|
|
|
|||
|
|
> **Screen ID:** FIL-003
|
|||
|
|
> **Source of Truth:** [Material Design 3](https://m3.material.io/)
|
|||
|
|
> **Tone:** Modern Industrial/Maker
|
|||
|
|
> **Theme:** Dark Mode, High-Contrast
|
|||
|
|
> **Last Updated:** 2026-04-20
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Objective
|
|||
|
|
|
|||
|
|
Design the primary filament intake experience — the "Smart Intake" workflow — optimized for both the kiosk (USB barcode scanner + touchscreen) and the mobile PWA (device camera + touch). This is the **most critical user flow** in Extrudex: every new spool enters the system through this workflow.
|
|||
|
|
|
|||
|
|
The workflow follows a strict **3-state linear progression**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
SCAN ──→ IDENTIFY ──→ UPDATE ──→ ✓ Complete
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Users must be able to:
|
|||
|
|
- **Scan:** Capture a barcode/QR code via camera (mobile) or USB scanner (kiosk).
|
|||
|
|
- **Identify:** Confirm or correct the scanned spool's identity (material, brand, color).
|
|||
|
|
- **Update:** Set initial weight and status, assign a location, and complete intake.
|
|||
|
|
|
|||
|
|
The workflow must feel **fast, confident, and forgiving** — operators process many spools in a session and can't afford friction or ambiguity at any step.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Screen Inventory
|
|||
|
|
|
|||
|
|
### Shared Across All States
|
|||
|
|
|
|||
|
|
| Element | MD3 Component | Notes |
|
|||
|
|
|---------|--------------|-------|
|
|||
|
|
| Top App Bar | `md-top-app-bar` (small) | Title + close/dismiss |
|
|||
|
|
| Step Indicator | Custom linear stepper | 3 dots/steps with active state |
|
|||
|
|
| Bottom Sheet | `md-bottom-sheet` | Contextual info/errors on mobile |
|
|||
|
|
|
|||
|
|
### Scan State (State 1 of 3)
|
|||
|
|
|
|||
|
|
| Element | MD3 Component | Notes |
|
|||
|
|
|---------|--------------|-------|
|
|||
|
|
| Camera Viewport | Custom + `video` element | Full-bleed camera with scanning overlay |
|
|||
|
|
| Viewport Frame | Custom overlay | Animated scanning rectangle |
|
|||
|
|
| Scan Result Chip | `md-chip` (assist) | Shows last scanned code |
|
|||
|
|
| Manual Entry Link | `md-text-button` | Fallback for unreadable codes |
|
|||
|
|
| USB Scanner Badge | `md-chip` (status) | Kiosk only — "Scanner Connected ✓" |
|
|||
|
|
|
|||
|
|
### Identification State (State 2 of 3)
|
|||
|
|
|
|||
|
|
| Element | MD3 Component | Notes |
|
|||
|
|
|---------|--------------|-------|
|
|||
|
|
| Spool Identity Card | `md-card` (filled) | Material, brand, color display |
|
|||
|
|
| Confidence Indicator | Custom bar | Match confidence (High/Medium/Low/Unknown) |
|
|||
|
|
| Material Selector | `md-dropdown-select` | If multiple matches or no match |
|
|||
|
|
| Brand Selector | `md-dropdown-select` | Brand selection |
|
|||
|
|
| Color Picker | Custom swatch grid | Filament color selection |
|
|||
|
|
| Finish + Modifier | `md-chip-set` (choice) | Material finish and modifier |
|
|||
|
|
| "Confirm" Button | `md-filled-button` | Primary CTA — proceed to Update |
|
|||
|
|
| "Rescan" Button | `md-outlined-button` | Go back to Scan state |
|
|||
|
|
|
|||
|
|
### Update State (State 3 of 3)
|
|||
|
|
|
|||
|
|
| Element | MD3 Component | Notes |
|
|||
|
|
|---------|--------------|-------|
|
|||
|
|
| Weight Input | `md-filled-text-field` + stepper | Grams input with +/- buttons |
|
|||
|
|
| Location Selector | `md-dropdown-select` | Assign to AMS slot or shelf |
|
|||
|
|
| Status Toggle | `md-chip-set` (choice) | Available / In Use |
|
|||
|
|
| QR Preview | `md-card` | Generated QR code preview |
|
|||
|
|
| "Complete Intake" Button | `md-filled-button` | Primary CTA — finalize |
|
|||
|
|
| "Print Label" Toggle | `md-switch` | Auto-print QR label on completion |
|
|||
|
|
| Summary Card | `md-card` (outlined) | Final summary before submission |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Layout Specification
|
|||
|
|
|
|||
|
|
### Shared Layout
|
|||
|
|
|
|||
|
|
#### Top App Bar (56dp mobile / 64dp kiosk)
|
|||
|
|
- **Leading:** Close (✕) → returns to Inventory List with unsaved-work confirmation if mid-flow
|
|||
|
|
- **Title:** "Smart Intake" (all states)
|
|||
|
|
- **Trailing:** None (deliberate — no distractions during intake)
|
|||
|
|
|
|||
|
|
#### Step Indicator (40dp, below App Bar)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
● ───── ○ ───── ○
|
|||
|
|
Scan Identify Update
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 3-step linear indicator
|
|||
|
|
- Active step: `primary` filled circle + `primary` connecting line
|
|||
|
|
- Completed step: `primary` filled circle + checkmark icon
|
|||
|
|
- Future step: `outline` circle + `outlineVariant` connecting line
|
|||
|
|
- Labels: `labelSmall` below each dot
|
|||
|
|
- Animation: Line fills with `primary` color as each step completes (300ms ease-out)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### STATE 1: SCAN
|
|||
|
|
|
|||
|
|
**Title:** "Scan Spool"
|
|||
|
|
|
|||
|
|
#### Layout — Mobile (Camera-based)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ ✕ Smart Intake │
|
|||
|
|
│──────────────────────────────────│
|
|||
|
|
│ ● ──── ○ ──── ○ │
|
|||
|
|
│ Scan Identify Update │
|
|||
|
|
│──────────────────────────────────│
|
|||
|
|
│ │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ┌────────────────┐ │ │
|
|||
|
|
│ │ │ ╔════════════╗│ │ │
|
|||
|
|
│ │ │ ║ VIEWPORT ║│ │ │
|
|||
|
|
│ │ │ ║ FRAME ║│ │ │
|
|||
|
|
│ │ │ ║ (animated) ║│ │ │
|
|||
|
|
│ │ │ ╚════════════╝│ │ │
|
|||
|
|
│ │ │ │ │ │
|
|||
|
|
│ │ │ ▲ Align barcode │ │ │
|
|||
|
|
│ │ │ within frame │ │ │
|
|||
|
|
│ │ └────────────────┘ │ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ LIVE CAMERA FEED │ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ [Last scan: 8901234567890] │
|
|||
|
|
│ │
|
|||
|
|
│ Can't scan? Enter manually │
|
|||
|
|
│ │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **Camera viewport:** Full-bleed video feed with 16:9 or 4:3 aspect ratio
|
|||
|
|
- **Scanning frame:** Animated rectangle (200dp × 120dp) centered in viewport
|
|||
|
|
- Corner brackets: `primary` color, 24dp corner length, 3dp stroke
|
|||
|
|
- Scan line: Horizontal `primary` line that sweeps top-to-bottom repeatedly (2s cycle)
|
|||
|
|
- On successful scan: Frame flashes `success` color (green) + brief haptic (mobile)
|
|||
|
|
- **Instruction text:** Below frame, `bodyMedium`, `onSurfaceVariant`
|
|||
|
|
- **Scan result chip:** Appears below camera when a code is detected
|
|||
|
|
- Shows scanned code value in monospace
|
|||
|
|
- Auto-advances to Identification state after 1.5s (with option to cancel)
|
|||
|
|
- **Manual entry link:** Text button below camera area for fallback
|
|||
|
|
|
|||
|
|
#### Layout — Kiosk (USB Scanner-based)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────┬──────────────────────────────────────────────┐
|
|||
|
|
│ NAV │ ✕ Smart Intake │
|
|||
|
|
│ RAIL │──────────────────────────────────────────────│
|
|||
|
|
│ │ ● ──── ○ ──── ○ │
|
|||
|
|
│ │ Scan Identify Update │
|
|||
|
|
│ │──────────────────────────────────────────────│
|
|||
|
|
│ │ │
|
|||
|
|
│ │ ┌──────────────────────┐ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ 📷 SCANNER READY │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ USB Scanner: ✓ │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ Scan a barcode or │ │
|
|||
|
|
│ │ │ QR code now... │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ ═══════════════ │ │
|
|||
|
|
│ │ │ (pulse animation) │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ └──────────────────────┘ │
|
|||
|
|
│ │ │
|
|||
|
|
│ │ Or: [Enter Barcode Manually] │
|
|||
|
|
│ │ │
|
|||
|
|
└──────┴──────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **No camera on kiosk** — USB HID barcode scanner is the primary input
|
|||
|
|
- **Scanner status card:** Shows connected/disconnected state
|
|||
|
|
- Connected: `success` icon + "Scanner Connected ✓"
|
|||
|
|
- Disconnected: `error` icon + "Scanner Not Found" + "Troubleshoot" button
|
|||
|
|
- **Pulse animation:** A horizontal line that pulses `primary` to indicate "waiting for scan"
|
|||
|
|
- **On scan:** The scanned code appears in a chip, auto-advances to Identification
|
|||
|
|
- **Manual entry:** Full keyboard (virtual) for typing barcode numbers
|
|||
|
|
|
|||
|
|
#### Scan State Elements
|
|||
|
|
|
|||
|
|
| Element | Description |
|
|||
|
|
|---------|-------------|
|
|||
|
|
| Primary CTA | None (scanning is automatic) — the camera/scanner IS the CTA |
|
|||
|
|
| Secondary Actions | Manual entry, close/dismiss |
|
|||
|
|
| Key Components | Camera viewport, scanning frame animation, scanner status (kiosk), scan result chip |
|
|||
|
|
| States | See below |
|
|||
|
|
|
|||
|
|
| State | Visual |
|
|||
|
|
|-------|--------|
|
|||
|
|
| **Initializing camera** | Shimmer loading in viewport + "Starting camera…" label |
|
|||
|
|
| **Ready (waiting for scan)** | Live feed + animated scan line + "Align barcode within frame" |
|
|||
|
|
| **Scanning (code detected)** | Frame flashes green + scan result chip appears + "Identified!" text |
|
|||
|
|
| **No match found** | Frame flashes yellow + chip + "Unknown barcode" → still advances to Identify (manual) |
|
|||
|
|
| **Camera denied** | Error card: "Camera access denied" + "Enter manually" button + permission settings link |
|
|||
|
|
| **Scanner disconnected (kiosk)** | Error card: "Scanner not detected" + "Reconnect scanner" + "Enter manually" |
|
|||
|
|
| **Multiple codes detected** | Shows list of detected codes as selectable chips → user picks correct one |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### STATE 2: IDENTIFY
|
|||
|
|
|
|||
|
|
**Title:** "Identify Spool"
|
|||
|
|
|
|||
|
|
#### Layout (Both platforms)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ ✕ Smart Intake │
|
|||
|
|
│──────────────────────────────────│
|
|||
|
|
│ ✓ ────● ──── ○ │
|
|||
|
|
│ Scan Identify Update │
|
|||
|
|
│──────────────────────────────────│
|
|||
|
|
│ │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ Scanned: 8901234567890 │ │
|
|||
|
|
│ │ Match: HIGH CONFIDENCE │ │
|
|||
|
|
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓░░ 92% │ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ Material │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ PLA Basic ▾│ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ Brand │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ Bambu Lab ▾│ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ Color │
|
|||
|
|
│ ● ● ● ● ● ● ● ● ● [Custom] │
|
|||
|
|
│ Bla Red Blu Grn Yel Org Pur Wh │
|
|||
|
|
│ │
|
|||
|
|
│ Finish Modifier │
|
|||
|
|
│ [Basic][Matte][Silk] [None] │
|
|||
|
|
│ │
|
|||
|
|
│ [Rescan] [Confirm →] │
|
|||
|
|
│ │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Confidence Indicator
|
|||
|
|
|
|||
|
|
A horizontal bar showing match confidence from the barcode lookup:
|
|||
|
|
|
|||
|
|
| Confidence | Visual | Color | Behavior |
|
|||
|
|
|-----------|--------|-------|----------|
|
|||
|
|
| **High** (≥80%) | Full green bar | `success` | Pre-fills all fields; user just confirms |
|
|||
|
|
| **Medium** (40-79%) | Yellow bar | `warning` | Pre-fills material/brand; color may need correction |
|
|||
|
|
| **Low** (<40%) | Orange bar | Custom orange | Pre-fills partial; more fields need manual input |
|
|||
|
|
| **Unknown** (0%) | Red bar | `error` | "Barcode not found" — all fields manual |
|
|||
|
|
|
|||
|
|
#### Material Selector
|
|||
|
|
- `mat-select` dropdown with searchable options
|
|||
|
|
- Options sourced from normalized taxonomy (MaterialBase list)
|
|||
|
|
- If confidence is High: pre-selected, field is in confirmed state (slight green tint)
|
|||
|
|
- If confidence is Low/Unknown: field is empty, highlighted for input
|
|||
|
|
|
|||
|
|
#### Brand Selector
|
|||
|
|
- `mat-select` dropdown with common brands
|
|||
|
|
- Searchable with "Other…" option that allows free-text entry
|
|||
|
|
|
|||
|
|
#### Color Picker
|
|||
|
|
- **Swatch grid:** 2 rows of circular color swatches (24dp each)
|
|||
|
|
- Common colors: Black, White, Red, Blue, Green, Yellow, Orange, Purple, Grey, Brown, Pink, Transparent
|
|||
|
|
- Tap swatch → selected (ring indicator)
|
|||
|
|
- "Custom" option → opens free-text color name input
|
|||
|
|
- Selected color stored as both name + hex value
|
|||
|
|
|
|||
|
|
#### Finish + Modifier Chips
|
|||
|
|
- **Finish (required):** `mat-chip-listbox` with single-select
|
|||
|
|
- Options: Basic, Matte, Silk, Sparkle, Metallic, Translucent
|
|||
|
|
- Default: "Basic"
|
|||
|
|
- **Modifier (optional):** `mat-chip-listbox` with single-select
|
|||
|
|
- Options: None, Carbon Fiber, Wood Fill, Glow-in-Dark, Marble, Gradient
|
|||
|
|
- Default: "None"
|
|||
|
|
|
|||
|
|
#### Identify State Elements
|
|||
|
|
|
|||
|
|
| Element | Description |
|
|||
|
|
|---------|-------------|
|
|||
|
|
| Primary CTA | **"Confirm"** filled button → advances to Update state |
|
|||
|
|
| Secondary Actions | **"Rescan"** outlined button → returns to Scan state |
|
|||
|
|
| Key Components | Confidence bar, Material dropdown, Brand dropdown, Color picker grid, Finish chips, Modifier chips |
|
|||
|
|
| Validation | Material and Brand are required. Color is required. Confirm button disabled until all required fields filled. |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### STATE 3: UPDATE
|
|||
|
|
|
|||
|
|
**Title:** "Update Spool"
|
|||
|
|
|
|||
|
|
#### Layout (Both platforms)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ ✕ Smart Intake │
|
|||
|
|
│──────────────────────────────────│
|
|||
|
|
│ ✓ ────✓ ──── ● │
|
|||
|
|
│ Scan Identify Update │
|
|||
|
|
│──────────────────────────────────│
|
|||
|
|
│ │
|
|||
|
|
│ Initial Weight │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ [-] 1000 [+] grams │ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ Quick: [250] [500] [750] [1000] │
|
|||
|
|
│ │
|
|||
|
|
│ Assign Location │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ Select location... ▾│ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ Status │
|
|||
|
|
│ [● Available] [○ In Use] │
|
|||
|
|
│ │
|
|||
|
|
│ ┌──────────────────────────┐ │
|
|||
|
|
│ │ Summary │ │
|
|||
|
|
│ │ PLA Basic - Matte Black │ │
|
|||
|
|
│ │ Bambu Lab • 1000g │ │
|
|||
|
|
│ │ AMS 2, Slot A3 • Avail │ │
|
|||
|
|
│ │ ID: EXT-2026-PLA-0043 │ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ┌──────┐ Print Label │ │
|
|||
|
|
│ │ │ QR │ [═══●═══] ON │ │
|
|||
|
|
│ │ │Preview│ │ │
|
|||
|
|
│ │ └──────┘ │ │
|
|||
|
|
│ └──────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ [◀ Back] [✓ Complete Intake] │
|
|||
|
|
│ │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Weight Input
|
|||
|
|
- **Stepper control:** Number input flanked by – and + buttons
|
|||
|
|
- Step size: 50g (adjustable via long-press to 10g/100g)
|
|||
|
|
- Minimum: 50g, Maximum: 5000g
|
|||
|
|
- Direct numeric keyboard input also available
|
|||
|
|
- **Quick-select chips:** Common spool sizes (250g, 500g, 750g, 1000g) — tap to set weight instantly
|
|||
|
|
- Active chip: `primaryContainer` fill
|
|||
|
|
- On kiosk: Larger chips (48dp height) for easy tapping
|
|||
|
|
- **Unit label:** "grams" suffix, non-editable, `onSurfaceVariant`
|
|||
|
|
|
|||
|
|
#### Location Selector
|
|||
|
|
- `mat-select` dropdown organized hierarchically:
|
|||
|
|
- **AMS Units** → Slot A1, A2, A3, A4
|
|||
|
|
- **External Holders** → Shelf B1, B2, B3
|
|||
|
|
- **Unassigned** (default)
|
|||
|
|
- Only shows available (unoccupied) locations
|
|||
|
|
- Selected location shows printer/host info as hint text
|
|||
|
|
|
|||
|
|
#### Status Toggle
|
|||
|
|
- `mat-chip-listbox` single-select:
|
|||
|
|
- **Available** (default): Green tonal chip + "Available" label
|
|||
|
|
- **In Use**: Blue tonal chip + "In Use" label
|
|||
|
|
- Most new spools are "Available" on intake — this is the safe default
|
|||
|
|
|
|||
|
|
#### Summary Card
|
|||
|
|
- **Outlined card** showing all intake details for final review:
|
|||
|
|
- Material name, brand, weight, location, status, tracking ID
|
|||
|
|
- QR code preview (small, 80dp) — auto-generated from tracking ID
|
|||
|
|
- "Print Label" toggle switch (default: ON on kiosk, OFF on mobile)
|
|||
|
|
- When ON: Bluetooth thermal printer will produce label after completion
|
|||
|
|
- Kiosk default ON because label printer is always connected
|
|||
|
|
- Mobile default OFF because printer may not be available
|
|||
|
|
|
|||
|
|
#### Update State Elements
|
|||
|
|
|
|||
|
|
| Element | Description |
|
|||
|
|
|---------|-------------|
|
|||
|
|
| Primary CTA | **"Complete Intake"** filled button → finalizes spool creation |
|
|||
|
|
| Secondary Actions | **"Back"** outlined button → returns to Identify state |
|
|||
|
|
| Key Components | Weight stepper, Quick-select chips, Location dropdown, Status toggle, Summary card, Print label switch, QR preview |
|
|||
|
|
| Validation | Weight is required (>0). Location is optional. Confirm button always enabled once weight is set. |
|
|||
|
|
|
|||
|
|
#### Completion Flow
|
|||
|
|
1. User taps "Complete Intake"
|
|||
|
|
2. API call creates spool record
|
|||
|
|
3. If "Print Label" is ON: send QR to thermal printer
|
|||
|
|
4. **Success state:** Step indicator animates all 3 steps complete + confetti-free success animation + "Spool Added ✓" snackbar
|
|||
|
|
5. **Two options appear:**
|
|||
|
|
- "Add Another Spool" → resets to Scan state (fastest path for batch intake)
|
|||
|
|
- "View Spool" → navigates to Spool Detail View for the newly created spool
|
|||
|
|
6. Auto-return to Inventory List after 10s if no action taken (kiosk only — prevents abandoned sessions)
|
|||
|
|
|
|||
|
|
### States
|
|||
|
|
|
|||
|
|
| State | Visual |
|
|||
|
|
|-------|--------|
|
|||
|
|
| **Default** | All fields at defaults, "Complete Intake" enabled once weight is set |
|
|||
|
|
| **Weight invalid** | Error text "Enter a weight between 50g and 5000g", button disabled |
|
|||
|
|
| **Location unavailable** | Dropdown shows "No available locations" + "Add as Unassigned" option |
|
|||
|
|
| **Submitting** | "Complete Intake" shows `mat-spinner` inline, disabled. Other elements non-interactive. |
|
|||
|
|
| **Success** | Green check animation, "Spool Added ✓", two option buttons |
|
|||
|
|
| **Error (API)** | Error snackbar + "Retry" action button. Form state preserved. |
|
|||
|
|
| **Error (Printer)** | Success still shows, but printer error snackbar: "Label print failed — reprint from spool detail" |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. UX Rationale
|
|||
|
|
|
|||
|
|
### Scan State
|
|||
|
|
|
|||
|
|
1. **Camera-first on mobile.** The phone's camera is the fastest barcode scanner available. No typing, no selecting — just point and scan. The animated viewport frame provides clear guidance on where to aim.
|
|||
|
|
|
|||
|
|
2. **USB scanner-first on kiosk.** The kiosk doesn't need a camera — the USB HID scanner is faster and more reliable than camera-based scanning. The "Scanner Ready" state gives immediate confidence that the hardware is working.
|
|||
|
|
|
|||
|
|
3. **Auto-advance after scan.** When a barcode is detected, the system should advance automatically. The 1.5s delay lets the user see what was scanned, but doesn't require a manual "next" tap. Speed matters when processing 20 spools in a row.
|
|||
|
|
|
|||
|
|
4. **Manual entry always available.** Barcodes get damaged. Camera angles are sometimes wrong. The manual fallback prevents workflow dead-ends.
|
|||
|
|
|
|||
|
|
### Identification State
|
|||
|
|
|
|||
|
|
5. **Confidence indicator reduces anxiety.** When a barcode is scanned, the user needs to know: "Did the system recognize this?" The confidence bar answers this instantly. High confidence → "just confirm." Low confidence → "you'll need to fill in details." This sets expectations.
|
|||
|
|
|
|||
|
|
6. **Pre-fill everything possible.** If the barcode matches a known product (UPC database or previous spools), pre-fill material, brand, and color. The user should confirm, not construct. Every pre-filled field is saved time.
|
|||
|
|
|
|||
|
|
7. **Color as swatches, not dropdown.** Filament color is visual — selecting from a text dropdown ("Red" vs "Crimson" vs "Scarlet") is ambiguous. Circular swatches are unambiguous and faster to scan.
|
|||
|
|
|
|||
|
|
8. **Finish/Modifier as chips.** These are small, mutually exclusive option sets. Chips are more glanceable and tappable than dropdowns for 2-6 options.
|
|||
|
|
|
|||
|
|
### Update State
|
|||
|
|
|
|||
|
|
9. **Weight stepper + quick-select.** Operators know spool weights (they're standard sizes). Quick-select chips (250g, 500g, 750g, 1000g) cover 90% of cases in a single tap. The stepper handles the remaining 10% where weight is non-standard.
|
|||
|
|
|
|||
|
|
10. **Location as optional.** New spools may not be immediately assigned to a printer. Forcing location assignment would create friction — some operators prefer to intake first, assign later.
|
|||
|
|
|
|||
|
|
11. **Summary card for confidence.** Before committing, the user sees everything in one place. This is the "receipt" pattern — it catches errors before they become data problems. The QR preview is especially important: operators verify the physical label will match.
|
|||
|
|
|
|||
|
|
12. **"Add Another" as primary post-completion action.** In a batch intake session (which is common), the operator wants to immediately scan the next spool. "Add Another" should be the most prominent post-completion option.
|
|||
|
|
|
|||
|
|
13. **Print label default differs by platform.** On kiosk, the thermal printer is connected and labels are expected. On mobile, the printer may be remote. The defaults reflect reality.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Visual Direction
|
|||
|
|
|
|||
|
|
### Typography (MD3 Type Scale)
|
|||
|
|
|
|||
|
|
| Role | Token | Size | Weight | Line Height |
|
|||
|
|
|------|-------|------|--------|-------------|
|
|||
|
|
| App Bar Title | `titleLarge` | 22sp | 400 | 28sp |
|
|||
|
|
| Step Labels | `labelSmall` | 11sp | 500 | 16sp |
|
|||
|
|
| Section Headers | `titleSmall` | 14sp | 500 | 20sp |
|
|||
|
|
| Field Labels | `bodyMedium` | 14sp | 400 | 20sp |
|
|||
|
|
| Field Values | `bodyLarge` | 16sp | 400 | 24sp |
|
|||
|
|
| Weight Display | `headlineMedium` | 28sp | 400 | 36sp |
|
|||
|
|
| Confidence Label | `labelLarge` | 14sp | 500 | 20sp |
|
|||
|
|
| Confidence % | `titleMedium` | 16sp | 500 | 24sp |
|
|||
|
|
| Scanned Code | `labelMedium` (monospace) | 12sp | 500 | 16sp |
|
|||
|
|
| Summary Lines | `bodyMedium` | 14sp | 400 | 20sp |
|
|||
|
|
| Tracking ID | `labelMedium` (monospace) | 12sp | 500 | 16sp |
|
|||
|
|
| CTA Button | `labelLarge` | 14sp | 500 | 20sp |
|
|||
|
|
| Instruction Text | `bodyMedium` | 14sp | 400 | 20sp |
|
|||
|
|
| Quick-Select Chip | `labelLarge` | 14sp | 500 | 20sp |
|
|||
|
|
|
|||
|
|
### Spacing (MD3 8dp grid)
|
|||
|
|
|
|||
|
|
| Element | Spacing |
|
|||
|
|
|---------|---------|
|
|||
|
|
| App Bar padding | 16dp horizontal, 12dp vertical |
|
|||
|
|
| Step indicator padding | 24dp horizontal, 12dp vertical |
|
|||
|
|
| Camera viewport | Full-bleed (0dp margin), 16dp rounded corners, overflow hidden |
|
|||
|
|
| Section gap | 16dp vertical |
|
|||
|
|
| Field label to input | 8dp |
|
|||
|
|
| Color swatch grid gap | 12dp |
|
|||
|
|
| Quick-select chip gap | 8dp |
|
|||
|
|
| Weight stepper internal | 12dp between elements |
|
|||
|
|
| Summary card padding | 16dp |
|
|||
|
|
| Button row gap | 12dp |
|
|||
|
|
| Bottom padding (before nav bar) | 16dp (mobile) / 0dp (kiosk — no bottom nav) |
|
|||
|
|
|
|||
|
|
### Color (MD3 Dark Theme — "Industrial Maker")
|
|||
|
|
|
|||
|
|
Same base palette as FIL-001 and FIL-002. Screen-specific additions:
|
|||
|
|
|
|||
|
|
| Role | Token | Value (Dark) | Usage |
|
|||
|
|
|------|-------|-------------|-------|
|
|||
|
|
| Camera Viewport BG | `surfaceContainerHighest` | `#36343B` | Camera area background (before camera starts) |
|
|||
|
|
| Scan Frame | `primary` | `#A8CEDA` | Animated scanning rectangle |
|
|||
|
|
| Scan Success Flash | Custom `success` | `#8BD0A0` | Flash on successful scan |
|
|||
|
|
| Confidence High | Custom `success` | `#8BD0A0` | High confidence bar |
|
|||
|
|
| Confidence High Container | Custom `successContainer` | `#00522E` | High confidence bar background |
|
|||
|
|
| Confidence Medium | Custom `warning` | `#FFD580` | Medium confidence bar |
|
|||
|
|
| Confidence Medium Container | Custom `warningContainer` | `#5D4200` | Medium confidence bar background |
|
|||
|
|
| Confidence Low | Custom orange | `#FFB784` | Low confidence bar |
|
|||
|
|
| Confidence Low Container | Custom orange container | `#5D3A00` | Low confidence bar background |
|
|||
|
|
| Confidence Unknown | `error` | `#F2B8B5` | Unknown/no match bar |
|
|||
|
|
| Confidence Unknown Container | `errorContainer` | `#8C1D18` | Unknown bar background |
|
|||
|
|
| Pre-filled Field Tint | `primaryContainer` | `#004D63` | Subtle green-blue tint on pre-filled inputs |
|
|||
|
|
| Quick-Select Active | `primaryContainer` | `#004D63` | Active chip fill |
|
|||
|
|
| Scanner Connected | Custom `success` | `#8BD0A0` | Kiosk scanner status |
|
|||
|
|
| Scanner Disconnected | `error` | `#F2B8B5` | Kiosk scanner status |
|
|||
|
|
|
|||
|
|
### Scan Frame Animation
|
|||
|
|
|
|||
|
|
The scanning rectangle uses a **sweep line** animation:
|
|||
|
|
- Horizontal line travels from top to bottom of the frame over 2 seconds
|
|||
|
|
- Line color: `primary` (#A8CEDA) with 40% opacity
|
|||
|
|
- Line width: 2dp
|
|||
|
|
- Frame corner brackets: 3dp stroke, `primary` color
|
|||
|
|
- On code detected: Frame corners pulse green (#8BD0A0) once, scan line stops
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Responsiveness
|
|||
|
|
|
|||
|
|
### Kiosk (800×480) — Scan State
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────┬──────────────────────────────────────────────┐
|
|||
|
|
│ NAV │ ✕ Smart Intake │
|
|||
|
|
│ RAIL │──────────────────────────────────────────────│
|
|||
|
|
│ │ ● ──── ○ ──── ○ │
|
|||
|
|
│ │ Scan Identify Update │
|
|||
|
|
│ │──────────────────────────────────────────────│
|
|||
|
|
│ │ │
|
|||
|
|
│ │ ┌──────────────────────────────────┐ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ 📷 SCANNER CONNECTED ✓ │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ Scan a barcode or QR code... │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ │ ═══════════════════ │ │
|
|||
|
|
│ │ │ (pulsing line) │ │
|
|||
|
|
│ │ │ │ │
|
|||
|
|
│ │ └──────────────────────────────────┘ │
|
|||
|
|
│ │ │
|
|||
|
|
│ │ Or: [Enter Barcode Manually] │
|
|||
|
|
│ │ │
|
|||
|
|
└──────┴──────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Kiosk (800×480) — Identify State
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────┬──────────────────────────────────────────────┐
|
|||
|
|
│ NAV │ ✕ Smart Intake │
|
|||
|
|
│ RAIL │──────────────────────────────────────────────│
|
|||
|
|
│ │ ✓ ────● ──── ○ │
|
|||
|
|
│ │ Scan Identify Update │
|
|||
|
|
│ │──────────────────────────────────────────────│
|
|||
|
|
│ │ Scanned: 8901234567890 HIGH ▓▓▓▓▓▓▓▓ 92% │
|
|||
|
|
│ │──────────────────────────────────────────────│
|
|||
|
|
│ │ Material: [PLA Basic ▾] │
|
|||
|
|
│ │ Brand: [Bambu Lab ▾] │
|
|||
|
|
│ │ Color: ● ● ● ● ● ● ● ● ● [C] │
|
|||
|
|
│ │ Finish: [Basic][Matte][Silk] Mod: [None] │
|
|||
|
|
│ │ │
|
|||
|
|
│ │ [Rescan] [Confirm →] │
|
|||
|
|
└──────┴──────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **Two-column layout for Material/Brand** on kiosk (side by side to save vertical space)
|
|||
|
|
- **Finish/Modifier on same row** to save space
|
|||
|
|
- **Color picker horizontal** with wrapping
|
|||
|
|
|
|||
|
|
### Kiosk (800×480) — Update State
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────┬──────────────────────────────────────────────┐
|
|||
|
|
│ NAV │ ✕ Smart Intake │
|
|||
|
|
│ RAIL │──────────────────────────────────────────────│
|
|||
|
|
│ │ ✓ ────✓ ──── ● │
|
|||
|
|
│ │ Scan Identify Update │
|
|||
|
|
│ │──────────────────────────────────────────────│
|
|||
|
|
│ │ Weight: [-] 1000 [+] g │
|
|||
|
|
│ │ Quick: [250] [500] [750] [1000] │
|
|||
|
|
│ │ Location: [AMS 2, Slot A3 ▾] │
|
|||
|
|
│ │ Status: [● Available] [○ In Use] │
|
|||
|
|
│ │──────────────────────────────────────────────│
|
|||
|
|
│ │ ┌──────────────────────────────────┐ │
|
|||
|
|
│ │ │ PLA Basic - Matte Black │ │
|
|||
|
|
│ │ │ Bambu Lab • 1000g • Slot A3 │ │
|
|||
|
|
│ │ │ EXT-2026-PLA-0043 QR ████ │ │
|
|||
|
|
│ │ │ Print Label [═══●═══] ON │ │
|
|||
|
|
│ │ └──────────────────────────────────┘ │
|
|||
|
|
│ │ │
|
|||
|
|
│ │ [◀ Back] [✓ Complete Intake] │
|
|||
|
|
└──────┴──────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **All content fits above fold** on kiosk — no scrolling needed
|
|||
|
|
- **Summary card compact** — inline with QR preview
|
|||
|
|
- **Buttons full-width** in action row
|
|||
|
|
|
|||
|
|
### Mobile PWA (375×812) — Scan State
|
|||
|
|
|
|||
|
|
As shown in Section 3 (full-bleed camera). Key differences:
|
|||
|
|
- Camera takes ~60% of screen height
|
|||
|
|
- Step indicator + instruction text in remaining space
|
|||
|
|
- Scan result chip floats above camera viewport
|
|||
|
|
|
|||
|
|
### Mobile PWA (375×812) — Identify State
|
|||
|
|
|
|||
|
|
As shown in Section 3. Key differences:
|
|||
|
|
- **Single column** layout — all fields stack vertically
|
|||
|
|
- **Scrollable** — Identification form exceeds screen height on small phones
|
|||
|
|
- **Color picker** wraps to 2 rows
|
|||
|
|
- **Buttons** fixed at bottom (sticky)
|
|||
|
|
|
|||
|
|
### Mobile PWA (375×812) — Update State
|
|||
|
|
|
|||
|
|
As shown in Section 3. Key differences:
|
|||
|
|
- **Scrollable** content
|
|||
|
|
- **Summary card** full-width
|
|||
|
|
- **Buttons** fixed at bottom (sticky)
|
|||
|
|
|
|||
|
|
### Key Adaptations
|
|||
|
|
|
|||
|
|
| Property | Kiosk (800×480) | Mobile (375×812) |
|
|||
|
|
|----------|-----------------|-------------------|
|
|||
|
|
| Scan input method | USB HID scanner | Device camera |
|
|||
|
|
| Scanner status card | Yes (connected/disconnected) | No (camera instead) |
|
|||
|
|
| Camera viewport | None | Full-bleed, ~60% height |
|
|||
|
|
| Form layout | Two-column where possible | Single column, scrollable |
|
|||
|
|
| Color picker | Horizontal with wrapping | Horizontal with wrapping (narrower) |
|
|||
|
|
| Quick-select chips | Larger (48dp height) | Standard (36dp height) |
|
|||
|
|
| Print label default | ON | OFF |
|
|||
|
|
| Post-completion auto-return | 10s → Inventory | No auto-return |
|
|||
|
|
| Bottom navigation | No (has rail) | Yes (80dp) |
|
|||
|
|
| Sticky buttons | At bottom of content | At bottom above nav bar |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Developer Handoff Notes
|
|||
|
|
|
|||
|
|
### Angular Material Components
|
|||
|
|
|
|||
|
|
| UI Element | Angular Material Component | Notes |
|
|||
|
|
|-----------|--------------------------|-------|
|
|||
|
|
| Top App Bar | `<mat-toolbar>` | Simple, non-collapsible. Close button on left. |
|
|||
|
|
| Step Indicator | Custom component | 3-dot stepper with animated connecting lines. Use `@angular/animations` for line fill. |
|
|||
|
|
| Camera Viewport | `<video>` + overlay `<div>` | Use `@angular/cdk/overlay` for scanning frame. Camera via `navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })`. |
|
|||
|
|
| Scanning Frame | Custom overlay | SVG or absolute-positioned divs for corner brackets. CSS animation for sweep line. |
|
|||
|
|
| Confidence Bar | Custom component | Horizontal bar with dynamic color + width. `<div>` with `[style.width]` binding. |
|
|||
|
|
| Material Selector | `<mat-select>` | With `<mat-option>`. Add `matSelectSearch` for searchable dropdown. |
|
|||
|
|
| Brand Selector | `<mat-select>` | Same as Material, plus "Other…" option with conditional `<input matInput>`. |
|
|||
|
|
| Color Picker | Custom component | Grid of circular `<button>` elements. Selected state: ring border. |
|
|||
|
|
| Finish Chips | `<mat-chip-listbox>` | Single-select. `mat-chip-option` elements. |
|
|||
|
|
| Modifier Chips | `<mat-chip-listbox>` | Single-select. Include "None" as default option. |
|
|||
|
|
| Weight Stepper | Custom component | `<button mat-icon-button>` (–/+) flanking `<input matInput type="number">`. |
|
|||
|
|
| Quick-Select Chips | `<mat-chip-listbox>` | Single-select. Values: 250, 500, 750, 1000. |
|
|||
|
|
| Location Selector | `<mat-select>` | Hierarchical with `<mat-optgroup>` for AMS/Shelves. |
|
|||
|
|
| Status Toggle | `<mat-chip-listbox>` | Two options: Available, In Use. |
|
|||
|
|
| Summary Card | `<mat-card>` outlined | Read-only display. Binding from form values. |
|
|||
|
|
| QR Preview | `qrcode` npm package | Small preview (80dp). Canvas or SVG rendering. |
|
|||
|
|
| Print Label Switch | `<mat-slide-toggle>` | Default ON (kiosk) / OFF (mobile). |
|
|||
|
|
| Complete Intake Button | `<button mat-raised-button>` | `color="primary"`. Shows `<mat-spinner>` inline when submitting. |
|
|||
|
|
| Rescan / Back | `<button mat-stroked-button>` | Navigates to previous state. |
|
|||
|
|
| Success State | Custom component | Green checkmark animation + option buttons. |
|
|||
|
|
| Snackbar | `<mat-snackbar>` | For success/error messages. Duration: 4s. |
|
|||
|
|
| Confirmation Dialog | `<mat-dialog>` | "Discard intake? Unsaved data will be lost." Cancel / Discard. |
|
|||
|
|
|
|||
|
|
### Barcode/QR Scanning Implementation
|
|||
|
|
|
|||
|
|
**Mobile (Camera):**
|
|||
|
|
- Use `@zxing/browser` or `html5-qrcode` library for camera-based barcode detection
|
|||
|
|
- Configure for: EAN-13, UPC-A, Code-128, QR, Data Matrix
|
|||
|
|
- Continuous scan mode (not snapshot) for faster detection
|
|||
|
|
- On detection: debounce 500ms to prevent duplicate reads
|
|||
|
|
- Vibrate on successful scan (if `Vibration API` available)
|
|||
|
|
|
|||
|
|
**Kiosk (USB HID Scanner):**
|
|||
|
|
- USB HID barcode scanners appear as keyboard input — they type the code + Enter
|
|||
|
|
- Listen for rapid key sequence ending in Enter on the Scan state
|
|||
|
|
- Buffer keystrokes; when Enter detected, treat buffered text as scanned code
|
|||
|
|
- Reset buffer on any pause >100ms between keystrokes
|
|||
|
|
- Debounce: ignore duplicate codes within 2s window
|
|||
|
|
|
|||
|
|
### Interaction Notes
|
|||
|
|
|
|||
|
|
1. **Auto-advance from Scan → Identify:** 1.5s delay after successful scan with a cancel affordance (tap scan result chip to cancel auto-advance).
|
|||
|
|
2. **Confidence-based pre-fill:** On entering Identify state, call API with scanned code. If match found, pre-fill form fields. Mark pre-filled fields with subtle `primaryContainer` background tint.
|
|||
|
|
3. **Weight stepper long-press:** Long-press on +/- changes step size (50→10 or 50→100). Visual feedback: tooltip showing current step size.
|
|||
|
|
4. **Quick-select chip sync:** Tapping a quick-select chip updates the stepper value and vice versa. They're two inputs to the same model.
|
|||
|
|
5. **Form validation (Identify):** "Confirm" button is disabled until Material and Color are set. Brand is strongly recommended but not blocking (can be "Unknown").
|
|||
|
|
6. **Form validation (Update):** "Complete Intake" button is disabled until Weight > 0.
|
|||
|
|
7. **Discard confirmation:** Back/close mid-flow triggers dialog: "Discard intake? This spool won't be saved." Cancel / Discard.
|
|||
|
|
8. **Post-completion "Add Another":** Resets all form state, returns to Scan state. Does NOT re-initialize camera (keep it running throughout session for speed).
|
|||
|
|
9. **Printer error handling:** If label printing fails, show snackbar but don't block the success state. The spool is already saved; label can be reprinted from Spool Detail.
|
|||
|
|
10. **Camera permission denied:** Show clear error with "Open Settings" link. Also show "Enter manually" as fallback.
|
|||
|
|
|
|||
|
|
### Accessibility
|
|||
|
|
|
|||
|
|
| Requirement | Implementation |
|
|||
|
|
|-------------|---------------|
|
|||
|
|
| Step indicator | `role="progressbar"`, `aria-valuenow="1/2/3"`, `aria-valuemin="1"`, `aria-valuemax="3"`, `aria-label="Step 1 of 3: Scan"` |
|
|||
|
|
| Camera viewport | `aria-label="Camera view for barcode scanning"`, `role="application"`, `aria-live="polite"` for scan result announcements |
|
|||
|
|
| Scanner status (kiosk) | `aria-live="polite"` — announces "Scanner connected" or "Scanner disconnected" |
|
|||
|
|
| Scan result | `aria-live="assertive"` — "Barcode 8901234567890 detected" |
|
|||
|
|
| Confidence bar | `role="progressbar"`, `aria-valuenow="92"`, `aria-label="Match confidence: 92 percent, High"` |
|
|||
|
|
| Material/Brand selects | Standard `mat-select` accessibility — `aria-label`, keyboard navigable |
|
|||
|
|
| Color picker | Each swatch: `aria-label="Black"`, `role="radio"`, `aria-checked="true/false"`. Radio group semantics. |
|
|||
|
|
| Weight stepper | `aria-label="Weight in grams"`, `role="spinbutton"`, `aria-valuenow`, `aria-valuemin="50"`, `aria-valuemax="5000"` |
|
|||
|
|
| Quick-select chips | `role="radiogroup"`, each chip `role="radio"`, `aria-label="500 grams"` |
|
|||
|
|
| Complete Intake button | `aria-label="Complete spool intake"` — changes to "Submitting…" during submission |
|
|||
|
|
| Close confirmation | `aria-labelledby` dialog title, `aria-describedby` dialog content. Focus trap. |
|
|||
|
|
| Motion reduction | Disable scan line animation, use static frame. Disable auto-advance (require manual "Next"). |
|
|||
|
|
| Keyboard flow | Tab: Close → Step indicator → Content → Buttons. Enter to activate. Escape to dismiss/close. |
|
|||
|
|
|
|||
|
|
### SignalR Integration
|
|||
|
|
|
|||
|
|
- After "Complete Intake" API call succeeds, the new spool is pushed to all connected clients via `SpoolAdded` hub event.
|
|||
|
|
- The Inventory List (FIL-001) will receive this event and add the spool with highlight animation.
|
|||
|
|
- If the user navigates to Inventory after intake, the new spool is already in the list — no manual refresh needed.
|
|||
|
|
|
|||
|
|
### Performance Notes
|
|||
|
|
|
|||
|
|
- Camera stream: request `640×480` resolution for barcode scanning (sufficient, saves battery/CPU)
|
|||
|
|
- Keep camera stream alive across "Add Another" cycles — only initialize once per session
|
|||
|
|
- Debounce API calls in Identify state: don't call the barcode lookup API more than once per 2s
|
|||
|
|
- Use `OnPush` change detection for all Smart Intake components
|
|||
|
|
- QR preview generation should be non-blocking — render in a `requestAnimationFrame` callback
|
|||
|
|
|
|||
|
|
### Routing
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
/intake → Redirect to /intake/scan
|
|||
|
|
/intake/scan → Scan State
|
|||
|
|
/intake/identify → Identification State
|
|||
|
|
/intake/update → Update State
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Each state is a separate route. This enables:
|
|||
|
|
- Browser back button works naturally (Update → Identify → Scan)
|
|||
|
|
- Deep-linking to specific states (e.g., for testing)
|
|||
|
|
- URL reflects current step (clear for the user and for analytics)
|