Retool Setup Guide: State Authority Subsystem
Retool Setup Guide: State Authority Subsystem
Prerequisites
- Retool account (free tier works for pilot)
- Airtable base
YFT-StateAuthoritycreated and populated - Airtable API key or Personal Access Token
Initial Setup
Connect Airtable as Resource
- Go to Retool → Resources → Create New
- Select “Airtable”
- Name:
YFT-Airtable - Enter your Airtable API key or Personal Access Token
- Test connection
- Save
Create App: YFT-WarehouseOps (T022)
- Create new app:
YFT-WarehouseOps - Set as “Web” app type (Mobile version created separately)
Phase 3: Garment Scanner (T023-T025)
Create Page: GarmentScanner
Layout
┌─────────────────────────────────────────────────────────┐
│ YFT Warehouse Ops - Garment Scanner │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────┐ │
│ │ Barcode: [________________] [Scan] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Garment Details │ │
│ │ ─────────────────────────────────────── │ │
│ │ ID: G001 │ │
│ │ SKU: TOP-CREW-BLK │ │
│ │ Size: M │ │
│ │ State: [Available] ● │ │
│ │ Condition: A │ │
│ │ Wear Count: 5 / 50 │ │
│ │ Cycle: (none) │ │
│ │ Box: (none) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Available Actions │ │
│ │ ─────────────────────────────────────── │ │
│ │ [Reserve] [Send to Refurbish] │ │
│ │ [Quarantine] [Retire] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Recent Events (this garment) │ │
│ │ ─────────────────────────────────────── │ │
│ │ 2024-01-15 Available → Reserved │ │
│ │ 2024-01-10 Refurbish → Available │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Queries
Query: getGarmentByBarcode
// Resource: YFT-Airtable
// Action: List Records
// Table: Garments
// Filter: {barcode} = ''
Query: getValidTransitions
// Resource: YFT-Airtable
// Action: List Records
// Table: TransitionRules
// Filter: AND(
// {entity_type} = 'Garment',
// {from_state} = '',
// {is_active} = TRUE()
// )
Query: getGarmentEvents
// Resource: YFT-Airtable
// Action: List Records
// Table: Events
// Filter: AND(
// {entity_type} = 'Garment',
// {entity_id} =
// )
// Sort: timestamp DESC
// Limit: 10
Query: updateGarmentState
// Resource: YFT-Airtable
// Action: Update Record
// Table: Garments
// Record ID:
// Fields: {
// "asset_state": ""
// }
Components
- barcodeInput (Text Input)
- Placeholder: “Scan or enter barcode”
- On submit: Run
getGarmentByBarcode
- garmentDetails (Container with Text components)
- Bind to
getGarmentByBarcode.data[0]
- Bind to
- stateIndicator (Badge/Tag)
- Value: ``
- Color: Map states to colors (Available=green, Reserved=yellow, etc.)
- lifecycleProgress (Progress Bar)
- Value: ``
- Max: ``
- actionButtons (Button Group)
- Dynamically show based on
getValidTransitions.data - Each button runs
updateGarmentStatewith selected to_state
- Dynamically show based on
- eventsTable (Table)
- Data:
getGarmentEvents.data - Columns: timestamp, from_state, to_state, actor_id
- Data:
Error Handling (T025)
Add error display component:
// After updateGarmentState, check for errors
if (updateGarmentState.error) {
// Look up error code
const errorCode = updateGarmentState.error.code || 'E003';
const errorInfo = getErrorCodes.data.find(e => e.error_code === errorCode);
showNotification({
type: 'error',
title: errorInfo?.error_name || 'Error',
message: errorInfo?.error_message || 'An error occurred',
description: errorInfo?.resolution_hint
});
}
Phase 4: Cycle Manager (T028-T032)
Create App: YFT-AdminConsole
- Create new app:
YFT-AdminConsole
Create Page: CycleManager
Layout
┌─────────────────────────────────────────────────────────┐
│ YFT Admin - Cycle Manager │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Filters │ │ Cycles List │ │
│ │ ─────────── │ │ ────────────────────────── │ │
│ │ State: [All ▼] │ │ ID User Week State │ │
│ │ User: [All ▼] │ │ 001 Alice W01 Sched │ │
│ │ Week: [ ] │ │ 002 Bob W01 Commit │ │
│ │ [Search] │ │ 003 Carol W01 Shipped │ │
│ └─────────────────┘ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ Cycle Detail (selected) ││
│ │ ──────────────────────────────────────────────────│││
│ │ User: Alice Test Week: 2024-W03 │ │
│ │ State: Scheduled Box: BOX-001 │ │
│ │ │ │
│ │ Timeline: │ │
│ │ ● Scheduled: 2024-01-15 10:00 │ │
│ │ ○ Committed: (pending) │ │
│ │ ○ Shipped: (pending) │ │
│ │ │ │
│ │ Garments: │ │
│ │ - G001 TOP-CREW-BLK (Reserved) │ │
│ │ - G015 BTM-JEAN-BLU (Reserved) │ │
│ │ │ │
│ │ Actions: │ │
│ │ [Commit Cycle] [Cancel Cycle] │ │
│ └─────────────────────────────────────────────────────┘│
│ │
│ [+ Create New Cycle] │
└─────────────────────────────────────────────────────────┘
Create Cycle Form (T029)
Modal or slide-out with:
- userSelect (Select)
- Options: Query Users where operational_state = ‘Active’
- Label: “User”
- weekIdInput (Text Input)
- Auto-suggest next week: ``
- Label: “Week ID”
- uniquenessCheck (Hidden query)
- Before create, check: No existing cycle with same user_id + week_id
- Show error if duplicate
- createCycleButton (Button)
- Disabled if uniqueness check fails
- On click: Create Cycles record with state = ‘Scheduled’
Phase 5: User Manager (T033-T036)
Create Page: UserManager
Layout
┌─────────────────────────────────────────────────────────┐
│ YFT Admin - User Manager │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐│
│ │ Users [+ Add User] ││
│ │ ───────────────────────────────────────────────── ││
│ │ Name State Anchor Cycles ││
│ │ Alice Test ● Active Monday 3 active ││
│ │ Bob Test ● Active Tuesday 2 active ││
│ │ Carol Test ⚠ HoldPay Wed 1 blocked ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ User Detail: Carol Test ││
│ │ ──────────────────────────────────────────────────│││
│ │ State: ⚠ HoldPayment │ │
│ │ Since: 2024-01-14 (2 days) │ │
│ │ │ │
│ │ Active Cycles: │ │
│ │ - Cycle 003: Scheduled (blocked by hold) │ │
│ │ │ │
│ │ Garments in Custody: 0 │ │
│ │ │ │
│ │ State History: │ │
│ │ 2024-01-14 Active → HoldPayment (payment_failed)│ │
│ │ 2024-01-01 (created) → Active │ │
│ │ │ │
│ │ Actions: │ │
│ │ [Remove Hold] [Apply Hold...] [Close Account] │ │
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
State Transition Controls (T036)
Apply Hold Modal:
- Select hold type: HoldPayment, HoldLogistics, HoldIdentity
- Enter reason (required)
- Confirm button
Remove Hold Action:
- Verify hold can be removed (e.g., payment restored)
- Transition to Active
- Re-evaluate blocked cycles
Phase 6: Box Packer (T037-T043)
Create Page: BoxPacker in YFT-WarehouseOps
Layout
┌─────────────────────────────────────────────────────────┐
│ YFT Warehouse - Box Packer │
├─────────────────────────────────────────────────────────┤
│ Box: [BOX-001 ▼] (Picking) Cycle: 003 │
│ User: Carol Test │
├─────────────────────────────────────────────────────────┤
│ ┌───────────────────────┐ ┌─────────────────────────┐ │
│ │ Planned Contents │ │ Scanned Contents │ │
│ │ ───────────────── │ │ ─────────────────── │ │
│ │ □ G001 TOP-CREW-BLK │ │ ✓ G001 TOP-CREW-BLK │ │
│ │ □ G015 BTM-JEAN-BLU │ │ │ │
│ │ □ G021 DRS-MIDI-BLK │ │ Scan: [________] [+] │ │
│ └───────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ ⚠ VARIANCE DETECTED ││
│ │ Missing: G015, G021 ││
│ │ ││
│ │ [Scan Missing Items] [Commit to Observed Set] ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ [Verify Pack] (disabled until variance resolved) │
└─────────────────────────────────────────────────────────┘
Variance Detection Logic (T040)
// Compare planned vs actual
const planned = new Set(box.planned_contents.map(g => g.barcode));
const actual = new Set(box.actual_contents.map(g => g.barcode));
const missing = [...planned].filter(b => !actual.has(b));
const extra = [...actual].filter(b => !planned.has(b));
const hasVariance = missing.length > 0 || extra.length > 0;
return {
hasVariance,
missing,
extra
};
Commit to Observed (T041, T043)
// When "Commit to Observed Set" clicked:
// 1. Create CompensatingAllocation event
await createEvent({
entity_type: 'Box',
entity_id: box.box_id,
from_state: 'Picking',
to_state: 'Picking', // State doesn't change
transition_type: 'Compensating',
metadata: JSON.stringify({
type: 'CompensatingAllocation',
planned: planned,
actual: actual,
missing: missing,
extra: extra,
reason: 'operator_committed_observed'
})
});
// 2. Allow verification to proceed
Phase 7: Event Viewer (T044-T048)
Create Page: EventViewer in YFT-AdminConsole
Layout
┌─────────────────────────────────────────────────────────┐
│ YFT Admin - Event Viewer │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐│
│ │ Filters ││
│ │ Entity Type: [All ▼] Entity ID: [____] ││
│ │ Transition: [All ▼] Date Range: [__] to [__] ││
│ │ [Search] [Clear] ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ Events (234 results) ││
│ │ ───────────────────────────────────────────────── ││
│ │ Time Entity From → To Actor ││
│ │ 01-15 10:00 Garment 1 Avail → Reserv system ││
│ │ 01-15 10:01 Box 1 Plan → Picking op_01 ││
│ │ 01-15 10:05 Garment 1 Reserv → Packed op_01 ││
│ │ 01-15 10:15 Cycle 3 Sched → Commit system ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ Event Detail ││
│ │ ──────────────────────────────────────────────────│││
│ │ Event ID: 1234 │ │
│ │ Entity: Garment #1 │ │
│ │ Transition: Available → Reserved (Normal) │ │
│ │ Timestamp: 2024-01-15 10:00:00 │ │
│ │ Actor: system │ │
│ │ Cycle: 3 │ │
│ │ Metadata: {"reason": "cycle_allocation"} │ │
│ │ │ │
│ │ [View Entity] [View Cycle] │ │
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
Entity History Reconstruction (T047)
Create a “Timeline” view component:
// Query all events for a specific entity
const entityHistory = events
.filter(e => e.entity_type === selectedType && e.entity_id === selectedId)
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
// Render as vertical timeline
// Each node shows: timestamp, from_state → to_state, actor
Phase 8: Dashboard & Inspection (T050-T051)
Create Page: Dashboard in YFT-AdminConsole
Summary cards with:
- Active Users count
- Available Garments count
- Open Cycles by state (pie chart)
- Recent Rejections (alert list)
Create Page: InspectionStation in YFT-WarehouseOps
For garments in ReceivedReturn state:
- List pending inspection
- Click to open inspection form
- Grade condition (A/B/C/D/F)
- Select routing: Refurbish, Repair, Quarantine, Retired
- One-click transition
Mobile App (T026-T027)
Create App: YFT-Mobile
- Create new app → Select “Mobile”
- Add GarmentScanner page (simplified version)
- Enable camera barcode scanning
- Deploy to Retool Mobile app
Key differences from web:
- Larger touch targets
- Camera scanner as primary input
- Simplified layout for one-hand operation
Verification Checklist
After Retool setup:
- Can scan garment and see details
- Can transition garment to valid states
- Invalid transitions show error
- Events logged for all transitions
- Can create cycle with uniqueness check
- Can manage user holds
- Box packer detects variance
- Event viewer shows history
- Dashboard shows correct counts
- Mobile app works with barcode camera