Demo Mode
The demo mode system provides a fully client-side simulation of the ETL pipeline. It makes zero API calls, zero Supabase queries, and zero file uploads. Everything runs from hardcoded data and setTimeout timers.
Purpose
The demo runs on the public landing page (/) without requiring authentication. When a visitor clicks “Preview Demo” in the LandingHero, the useDemoMode hook fires and the same ProgressStepper and ResultsPanel components render with simulated data — giving users a realistic preview of what the pipeline does.
The useDemoMode Hook
File: hooks/useDemoMode.ts
State
{
active: boolean; // Whether the demo is running
state: string; // Current pipeline state (idle/uploading/analyzing/extracting/complete)
progress: JobProgress; // Full progress object, same shape as real pipeline
start: () => void; // Begin simulation
reset: () => void; // Stop and clear
}How It Works
- Calling
start()setsactive = trueand begins stepping throughDEMO_STEPS - Each step updates
stateandprogresswith new values, then schedules the next step viasetTimeoutwith the step’sdelay - Milestones accumulate exactly like the real pipeline — each step adds a new
Milestoneentry withDate.now()timestamp - When the final step is reached (
complete), the simulation stops - Calling
reset()clears the timeout, resets all state to defaults, and setsactive = false - The timer is cleaned up on component unmount via a
useEffectreturn
The 23 Demo Steps
The simulation runs through 23 scripted steps with carefully timed delays:
| # | State | Pct | Message | Delay |
|---|---|---|---|---|
| 1 | uploading | 5 | Uploading your File… | 600ms |
| 2 | analyzing | 8 | Opening your File… | 800ms |
| 3 | analyzing | 12 | Reading the File… | 500ms |
| 4 | analyzing | 15 | Found 3 Sheets with 1,231 rows | 700ms |
| 5 | analyzing | 16 | Scanning 3 Sheets for Tables… | 1200ms |
| 6 | analyzing | 19 | Found 5 Tables across 3 Sheets | 600ms |
| 7 | analyzing | 22 | Picking the primary data Sheet… | 2000ms |
| 8 | analyzing | 25 | Looking at column headers… | 1500ms |
| 9 | analyzing | 28 | Identifying charge layout per Table… | 1200ms |
| 10 | analyzing | 32 | Mapping columns to schema… | 1000ms |
| 11 | analyzing | 38 | Locking in the layout… | 500ms |
| 12 | analyzing | 40 | Found: Montrose at Vintage Park (02/10/2025) | 800ms |
| 13 | extracting | 50 | Extracting Units in 6 passes… | 800ms |
| 14 | extracting | 53 | Reading through each Table… | 1500ms |
| 15 | extracting | 55 | Got 18 Units so far… | 1500ms |
| 16 | extracting | 62 | Got 36 Units so far… | 2000ms |
| 17 | extracting | 68 | Got 54 Units so far… | 1800ms |
| 18 | extracting | 75 | Got 72 Units so far… | 2000ms |
| 19 | extracting | 82 | Got 89 Units so far… | 1500ms |
| 20 | extracting | 88 | Found 106 Units | 1000ms |
| 21 | extracting | 92 | Validating 106 Units… | 600ms |
| 22 | extracting | 95 | Building your clean File… | 500ms |
| 23 | complete | 100 | All done! | 0ms |
Total simulated duration is approximately 20 seconds.
Message Flow with Entity Terminology
The milestone messages deliberately use the Sheet/Table/Unit hierarchy to teach users the pipeline’s mental model:
File → Sheets → Tables → Units → Clean Output
- Sheets are discovered first (the raw Excel worksheets)
- Tables are found within Sheets (distinct data regions)
- Units are extracted from Tables (individual apartment/unit records)
These entity terms are color-coded by the ProgressStepper component’s colorizeMessage function.
Demo Property Data
At step 12 (40% progress), the demo reveals the detected property:
| Field | Value |
|---|---|
| Property Name | Montrose at Vintage Park |
| Report Date | 2025-02-10 |
| Original Filename | Montrose_RentRoll_Feb2025.xlsx |
| File Size | 380,000 bytes |
| Total Rows | 1,231 |
Demo Results
When the simulation reaches complete, the progress object is populated with final results:
| Field | Value |
|---|---|
unit_count | 106 |
error_count | 1 |
warning_count | 5 |
total_chunks | 6 |
Unit Breakdown
{
"successful": 100,
"flagged": 5,
"failed": 1,
"total": 106
}Errors
Unit 2107: missing lease end date
Warnings
Row 45 (unit=2205): total_charges (1525) != sum (1525.00)Row 18 (unit=1402): Occupied unit with zero base rentRow 67 (unit=2501): duplicate unit_id '2501'Row 89 (unit=3102): total_charges (1450) != sum (1415.00)Row count mismatch: extracted 106 units but expected ~112 from data range
Demo Comparison Data
The DEMO_COMPARISON_DATA array contains 6 hardcoded ComparisonUnit objects representing Units from the Montrose property. These are used when the user clicks “Compare Source & Output” on the demo results panel.
Units
| Unit ID | Status | Tenant | Sqft | Floor Plan | Key Details |
|---|---|---|---|---|---|
| 1101 | Occupied | Johnson, Sarah | 842 | A1 | Base rent $1,195, pet fee $35 |
| 1102 | Vacant | — | 842 | A1 | Market rent only, no charges |
| 1203 | Occupied | Martinez, David & Ana | 1124 | B2 | Base rent $1,600, pet $70, parking $75 |
| 2107 | Occupied | Chen, Wei | 756 | S1 | Missing lease end date (flagged as error) |
| 2205 | Notice | Patel, Ravi | 1124 | B2 | Has move-out date 03/15/2025 |
| 3301 | Occupied | Williams, Tanya | 982 | A2 | Has concession -$100 |
Difference Types
Each Unit includes a differences array showing what changed between source and cleaned output:
| Type | Meaning | Example |
|---|---|---|
transformed | Value was reformatted or normalized | "OCC" becomes "Occupied", "$1,250.00" becomes 1250, "01/15/2024" becomes "2024-01-15" |
missing | Expected value was not found in source | Unit 2107 has no lease end date in the source |
Color-Coded Entity System
Throughout the demo (and the real pipeline), three entity types are visually distinguished in milestone messages:
| Entity | Color | Token / Hex | Usage |
|---|---|---|---|
| File | Dark | theme.other.textPrimary | The uploaded file |
| Sheet | Green | theme.other.greenAccent (#2e6b4f) | Excel worksheets |
| Table | Brown | theme.other.brownAccent (#6b5c3e) | Data regions within Sheets |
| Unit | Blue | theme.other.blueAccent (#3d8ab5) | Individual apartment/Unit records |
These colors are applied by ProgressStepper’s colorizeMessage function, which splits messages on /(File|Sheets?|Tables?|Units?)/g and wraps matches in styled <Text> spans with fw={600} (semibold) and fs="italic". The <E> and <Flow> components in this documentation mirror that same color system.
Integration
The landing page (/) wires everything together:
const demo = useDemoMode();
// In the JSX:
{!demo.active && <LandingHero onPreviewDemo={demo.start} />}
{demo.active && demo.state !== "complete" && (
<ProgressStepper pipelineState={demo.state} progress={demo.progress} />
)}
{demo.state === "complete" && (
<ResultsPanel progress={demo.progress} jobId="demo" onReset={demo.reset} isDemo />
)}When isDemo is true, the ResultsPanel disables the download button and the ComparisonView uses DEMO_COMPARISON_DATA instead of fetching from the API.