Interactive onboarding instructions for participants of the PropTech Conference on 13 November 2025. This workshop guide app provides step-by-step instructions for setting up development tools and building applications.
- In AI chat window type: "Get the latest code from main branch at @https://github.com/YarnMeister/workshop-guide-app"
- In AI chat window type: "Start a new feature branch for (insert short description)"
- Make changes as needed by asking AI assistant to update the app in multiple chat requests
- In terminal window type:
npm run dev(this starts the app with latest changes) - Copy paste the URL in the terminal into your browser to test the app
- In AI chat window type: "Merge the current feature branch to main on remote and delete the feature branch once merged"
- This copies the changes you made back to GitHub so that others can see your awesome changes
The workshop guide consists of 8 onboarding steps plus Welcome and Dashboard pages:
- Welcome (
/) - Participant code entry and authentication - Setup Tools (
/onboarding/step/1) - Install Git, Node.js, Void Editor, configure GitHub - Define the App Vision (
/onboarding/step/2) - Fill out PRD form with accordion sections - Generate the Prototype (
/onboarding/step/3) - AI-enhanced prompt for Lovable - Export to GitHub (
/onboarding/step/4) - Instructions for exporting from Lovable - Learn the Vibe Coding Flow (
/onboarding/step/5) - Concepts, glossary, best practices - Make Your First Commit (
/onboarding/step/6) - Git workflow with Void Editor - Extend Your App (
/onboarding/step/7) - Placeholder for advanced features - Launch to the Web (
/onboarding/step/8) - Deploy to Vercel - Congrats (
/congrats) - Completion confirmation and next steps (shown after completing "Extend Your App")
- Framework: React 18.3.1 + TypeScript
- Build Tool: Vite 5.4.19
- Routing: React Router v6
- State Management: React hooks + localStorage + TanStack Query
- Styling: Tailwind CSS + tailwindcss-animate
- UI Components: shadcn/ui (Radix UI primitives)
- Forms: react-hook-form + zod validation
- Icons: Lucide React
- Runtime: Node.js
- Framework: Express.js (unified across local and production)
- Database: Neon Postgres (serverless PostgreSQL)
- Database Client: pg (node-postgres) with connection pooling
- ORM: Drizzle ORM with type-safe schema definitions
- Migrations: Drizzle Kit with automated migration generation and safety checks
- Session Management: Cookie-based with HMAC signing (HttpOnly, 8-hour expiration)
- External APIs: OpenRouter API (Claude 3.5 Sonnet for AI enhancement)
The app uses a single Express.js application that runs identically in both local development and production:
- Local Development: Express server runs on port 3001, proxied by Vite dev server on port 8080
- Production (Vercel): Same Express app runs as a serverless function via
api/index.ts - Key Benefit: Identical behavior in both environments - no dual architecture complexity
How it works:
server/index.ts- Main Express application with all routes and middlewareapi/index.ts- Thin wrapper that imports and exports the Express app for Vercel- In local dev:
server/index.tsstarts HTTP server on port 3001 - In production: Vercel invokes the exported app from
api/index.ts - Same database connection pooling, same routes, same logic everywhere
- Node.js (LTS version)
- npm or yarn
# Clone the repository
git clone https://github.com/YarnMeister/workshop-guide-app.git
# Navigate to project directory
cd workshop-guide-app
# Install dependencies
npm install
# Start development server (runs both client and server)
npm run dev
# Or run separately:
npm run dev:client # Vite dev server on port 8080
npm run dev:server # Express server on port 3001Create a .env.local file in the root directory (see .env.example for template):
# OpenRouter API Key (for AI enhancement features)
VITE_OPEN_ROUTER_API_KEY=sk-or-v1-your-key-here
# Required for participant authentication
COOKIE_SECRET=your-secret-key-here-min-32-chars
# Neon Database URL (PostgreSQL connection string)
DATABASE_URL=postgresql://user:password@host/database?sslmode=require
# CORS origin (optional, defaults to *)
ALLOWED_ORIGIN=http://localhost:8080
# Node environment
NODE_ENV=developmentImportant Notes:
COOKIE_SECRETmust be at least 32 characters for securityDATABASE_URLmust be a valid PostgreSQL connection string (Neon provides this)- All participant data is stored in the Neon database
- For production, set these in Vercel environment variables
workshop-guide-app/
├── src/
│ ├── components/ # React components
│ │ ├── ui/ # shadcn/ui components
│ │ ├── Header.tsx # App header with participant name
│ │ ├── Breadcrumb.tsx # Progress sidebar navigation
│ │ ├── PRDForm.tsx # PRD form component (accordion)
│ │ └── ErrorBoundary.tsx # Error handling
│ ├── pages/ # Route pages
│ │ ├── Welcome.tsx # Landing page with code entry
│ │ ├── OnboardingStep.tsx # Main step page (handles all 8 steps)
│ │ ├── Congrats.tsx # Completion/congratulations page
│ │ └── NotFound.tsx # 404 page
│ ├── hooks/ # Custom React hooks
│ │ ├── useParticipant.ts # Participant state & session
│ │ └── useWorkshopProgress.ts # Progress tracking
│ ├── services/ # API clients
│ │ ├── participant.ts # Participant API endpoints
│ │ └── openrouter.ts # AI enhancement service
│ ├── data/ # Static data
│ │ └── steps.ts # Step definitions & content
│ ├── utils/ # Utilities
│ │ ├── storage.ts # localStorage helpers
│ │ └── prdFormatter.ts # PRD formatting for AI
│ └── App.tsx # Root component with routing
├── server/ # Express server (local dev)
│ ├── index.ts # API routes & session management
│ └── database.ts # Database connection & query helpers
├── api/ # Vercel serverless functions
│ └── index.ts # Entry point for production
├── drizzle/ # Database schema & migrations
│ ├── schema.ts # TypeScript schema definitions
│ └── migrations/ # Generated SQL migration files
├── scripts/ # Build & migration scripts
│ ├── lint-migrations.cjs # Migration safety checks
│ └── prebuild-migrations.cjs # Auto-run migrations on deploy
├── public/ # Static assets (images, favicon)
├── drizzle.config.ts # Drizzle ORM configuration
└── vercel.json # Vercel deployment configuration
- Participant Authentication: Code-based entry with session management
- Step-by-step guidance: Clear instructions for each workshop phase
- Progress tracking: Persistent localStorage with binary sliders for step completion
- PRD Form: Accordion-based form with 9 sections for app vision definition
- AI Enhancement: Transforms PRD into optimized Lovable prompts via OpenRouter API
- Copy-to-clipboard: Easy command copying for terminal instructions
- Responsive design: Works on desktop and mobile devices
- Interactive navigation: Breadcrumb sidebar with progress indicators
- Validation: Next button disabled until all steps are completed (Step 1)
- Visual feedback: Progress summary, completion indicators, toast notifications
- Session persistence: Auto-resume from last step on return visit
- Dual backend: Express for local dev, Vercel serverless for production
- Secure sessions: HMAC-signed cookies with HttpOnly flag
- API key masking: Secure display of participant API keys
- Error handling: Error boundaries, toast notifications, graceful fallbacks
- Caching: AI prompts cached in localStorage to avoid redundant API calls
The API supports two authentication methods:
- Cookie-based (Web App): Session cookies for browser-based access
- API Key (External Clients): Bearer token authentication for scripts and external applications
For external API access, see EXTERNAL_API_ACCESS.md for comprehensive documentation including:
- Authentication setup
- Available endpoints
- Rate limiting (100 requests/minute)
- Node.js integration examples
- Error handling
-
POST /api/claim- Claim participant code and create session- Body:
{ code: string } - Returns:
{ success: boolean, participantId: string, name: string, apiKeyMasked: string }
- Body:
-
GET /api/session- Check current session status- Returns:
{ authenticated: boolean, participantId?: string, name?: string }
- Returns:
-
POST /api/reveal-key- Reveal full API key (requires valid session)- Returns:
{ success: boolean, apiKey: string, apiKeyMasked: string }
- Returns:
-
POST /api/logout- Clear session cookie- Returns:
{ success: boolean }
- Returns:
-
GET /api/health- Health check endpoint- Returns:
{ status: string, env: object }
- Returns:
All data endpoints support both cookie and API key authentication:
GET /api/insights/suburbs- Get suburb-level price insightsGET /api/insights/property-types- Get property type insightsGET /api/insights/price-trends- Get time-series price trendsGET /api/insights/sale-types- Get sale type insightsGET /api/insights/market-stats- Get overall market statisticsGET /api/properties/search- Search properties with filters
Rate Limiting: API key authentication is rate limited to 100 requests/minute per participant.
- OpenRouter API: Used for AI prompt enhancement
- Endpoint:
https://openrouter.ai/api/v1/chat/completions - Model:
anthropic/claude-3.5-sonnet - Called when transitioning from Step 2 to Step 3
- Endpoint:
The app uses Drizzle ORM for type-safe database schema definitions and automated migrations:
Database schema is defined in TypeScript (drizzle/schema.ts):
export const propertySales = pgTable('property_sales', {
financialYear: integer('financial_year'),
activeMonth: date('active_month'),
state: char('state', { length: 3 }),
suburb: varchar('suburb'),
priceSearchSold: integer('price_search_sold'),
// ... other columns
}, (table) => ({
// Performance indexes for 400k+ rows
stateIdx: index('property_sales_state_idx').on(table.state),
suburbIdx: index('property_sales_suburb_idx').on(table.suburb),
// ... composite indexes for common query patterns
}));Benefits:
- Type-safe database queries
- Automatic TypeScript types from schema
- Version-controlled schema changes
- Performance indexes defined in code
# Generate migration from schema changes
npm run db:generate
# Check migration status
npm run db:status
# Apply migrations to database
npm run db:migrate
# Lint migrations for safety
npm run db:lint:migrations- Make Schema Changes: Edit
drizzle/schema.ts - Generate Migration: Run
npm run db:generate- Creates SQL file in
drizzle/migrations/ - Generates metadata and snapshots
- Creates SQL file in
- Review Migration: Check generated SQL for correctness
- Lint Migration: Run
npm run db:lint:migrations- Blocks destructive operations (DROP, TRUNCATE, DELETE)
- Validates migration structure
- Apply Locally: Run
npm run db:migrate- Tests migration on local/dev database
- Commit Changes: Commit schema + migration files to Git
- Deploy: Push to main → Vercel auto-runs migrations in production
The migration linter (scripts/lint-migrations.cjs) prevents common mistakes:
- ❌ Blocks destructive operations without explicit approval
- ❌ Prevents manual transactions (Drizzle handles this)
- ❌ Detects empty migrations
⚠️ Warns about TODO/FIXME comments
To allow destructive operations, add comment to migration:
-- allow-destructive
DROP TABLE old_table;Migrations run automatically during Vercel production builds:
- Prebuild Hook:
npm run prebuild→scripts/prebuild-migrations.cjs - Lint Migrations: Validates all migration files
- Apply Migrations: Runs pending migrations against production DB
- Build App: Proceeds with Vite build if migrations succeed
Environment Detection:
- Production: Runs migrations automatically
- Preview/Dev: Skips migrations (run manually)
The current schema includes 9 performance indexes on the property_sales table (428k+ rows):
Single-column indexes:
state- State filteringsuburb- Suburb searches and GROUP BYproperty_type- Property type analysisactive_month- Time-series queriesprice_search_sold- Price aggregations and sorting
Composite indexes:
(state, suburb)- State + suburb filtering(state, property_type)- State + type analysis(state, active_month)- State + time-series(state, price_search_sold)- State + price queries
Expected Performance:
- Without indexes: 2-10 seconds per aggregation
- With indexes: 50-500ms per aggregation
- With caching: 1-10ms for cached results
The API implements in-memory caching for read-heavy endpoints:
// Cache configuration
const cache = new Map<string, CacheEntry>();
// Cached endpoints (5-10 minute TTL)
GET /api/insights/suburbs // 5 min cache
GET /api/insights/property-types // 5 min cache
GET /api/insights/market-stats // 10 min cacheBenefits:
- Reduces database load for 20 concurrent users
- Improves response times (1-10ms for cached data)
- Automatic cache expiration every 5 minutes
- Manual cache clearing via
POST /api/cache/clear
Database connections are optimized for serverless environments:
// Serverless-optimized pool settings
max: 10, // Reduced for serverless (Vercel runs multiple instances)
min: 0, // Allow pool to scale to zero when idle
idleTimeoutMillis: 10000, // Fast cleanup (10s)
allowExitOnIdle: true, // Allows process to exit when idleWhy these settings:
- Vercel runs multiple serverless instances
- Each instance has its own connection pool
- Lower
maxprevents connection exhaustion allowExitOnIdleenables proper serverless shutdown
Slow queries (>1 second) are automatically logged:
⚠️ Slow query (1234ms): SELECT suburb, AVG(price_search_sold)...This helps identify performance bottlenecks during development and production.
The app is configured for Vercel deployment:
-
Build Configuration (
vercel.json):- Build command:
npm run build - Output directory:
dist - API routes:
/api/*→ serverless functions - Function memory: 1024MB
- Function timeout: 10 seconds
- Build command:
-
Environment Variables (set in Vercel dashboard):
DATABASE_URL- Neon PostgreSQL connection stringCOOKIE_SECRET- HMAC signing secretVITE_OPEN_ROUTER_API_KEY- OpenRouter API key (for AI features)ALLOWED_ORIGIN- CORS origin (optional)NODE_ENV- Set toproduction
-
Deployment Process:
# Build locally to test npm run build # Deploy to Vercel vercel deploy # Or push to main branch (auto-deploys if connected)
- Create a feature branch for your changes
- Follow the established style guide for content
- Test your changes locally with
npm run dev - Ensure environment variables are configured
- Test API endpoints locally before deploying
- Merge to main when ready to deploy
This project is part of the REA Vibe Coding Workshop for the PropTech Conference 2025.
Based on the Setup Tools page implementation, here are the styling patterns for consistent content creation:
text-3xl font-bold tracking-tight sm:text-4xl- Usage: Main page titles (e.g., "Setup Your Development Environment")
- Size: Large, bold, responsive
- Example: Page title at the top of each step
font-semibold text-lg- Usage: Main section titles within steps (e.g., "Create Your Accounts")
- Size: Medium, semibold
- Example: Step titles like "Install Void Editor", "Connect Git to Your GitHub Account"
font-medium text-sm- Usage: Subsection titles (e.g., "GitHub Account", "Set your name")
- Size: Small, medium weight
- Example: Individual instruction titles within sections
text-sm text-muted-foreground- Usage: Instructions, descriptions, and explanatory text
- Size: Small, muted color
- Example: Step descriptions, instruction text, explanations
overflow-x-auto rounded-md bg-muted p-4 text-sm- Usage: Code snippets, commands, and technical content
- Background: Light grey (
bg-muted) - Padding: 4 units
- Example: Terminal commands, code examples, URLs
flex items-center justify-between rounded-md bg-muted p-3 text-sm- Usage: Individual commands that can be copied to clipboard
- Features: Copy button, grey background, compact padding
- Example:
git config --global user.name "Your Name"
The app uses a combination of React hooks and localStorage for state management:
-
Participant State (
useParticipanthook):- Manages participant authentication
- Handles session validation
- Stores participant ID, name, and masked API key
- API key stored in memory only (never persisted)
-
Progress State (
useWorkshopProgresshook):- Tracks current step ID
- Stores completed pages array
- Manages setup page todos (Step 1)
- Persists PRD answers
- Stores template texts (write specs, prototype)
- Handles AI enhancement errors
-
Storage:
localStorage: Storesworkshop_progressobject- Session cookies:
participant_session(HttpOnly, signed, 8-hour expiration)
-
Participant Authentication:
User enters code → POST /api/claim → Validate code → Create signed cookie → Return participant data → Store in localStorage + React state -
Progress Tracking:
User actions → updateProgress() → Save to localStorage → Update React state → Persist across sessions -
AI Enhancement (Step 2 → Step 3):
PRD form data → formatPRDForAI() → Check cache → Call OpenRouter API → Transform to Lovable prompt → Cache result → Display in Step 3
- Dual Authentication: Supports both cookie-based (web app) and API key-based (external clients) authentication
- Session Management: HMAC-signed cookies prevent tampering
- API Key Security: Full keys never persisted, only displayed on-demand
- Rate Limiting: API key authentication limited to 100 requests/minute per participant
- CORS: Configurable origin restrictions (supports wildcard for workshop, whitelist for production)
- HttpOnly Cookies: Prevents XSS attacks on session data
- Secure Flag: Enabled in production for HTTPS-only cookies
- HTTPS Only: API keys transmitted securely over HTTPS in production
These are key architectural decisions and patterns used throughout the application:
The app uses Express.js for local development and Vercel serverless functions for production. The same Express app (server/index.ts) is imported by the Vercel function entry point (api/index.ts), ensuring consistent behavior across environments.
Why: Allows full Express features locally (hot reload, debugging) while leveraging Vercel's serverless infrastructure in production.
The app maintains session state in two places: signed HTTP-only cookies (server-side) and localStorage (client-side). The useParticipant hook synchronizes these on mount, checking cookie validity before trusting localStorage data.
Why: Provides resilience - if cookies expire, localStorage can restore session; if localStorage is cleared, cookies can restore it. This dual-persistence prevents accidental logouts.
PRD content is hashed (using base64 encoding) and used as a cache key in localStorage. This prevents redundant API calls when users navigate back to Step 3 or re-enter the same PRD content.
Why: Reduces API costs, improves performance, and provides better UX (instant loading of previously generated prompts).
The storage utilities (utils/storage.ts) include migration logic to handle old localStorage formats. When loading progress, it checks for missing fields and initializes them with defaults.
Why: Allows seamless updates without breaking existing user sessions or requiring data migration scripts.
The app works without JavaScript for basic content display, though full interactivity requires JS. Error boundaries catch React errors gracefully, and API failures show user-friendly toast messages.
Why: Ensures the app remains functional even if JavaScript fails or APIs are unavailable, providing a better user experience.
The OnboardingStep component handles all 8 steps dynamically based on step data from steps.ts. This single component renders different content structures (sections, tabs, forms) based on step configuration.
Why: Reduces code duplication, makes adding new steps easier, and ensures consistent UI patterns across all steps.
API keys are never stored in localStorage. When needed, the app calls /api/reveal-key which validates the session cookie before returning the full key. The key is stored in React state (memory only) for the session duration.
Why: Maximum security - even if localStorage is compromised, API keys aren't exposed. Keys are only revealed on-demand with valid authentication.
The app includes a custom renderTextWithLinks function that processes both markdown-style links [text](url) and plain URLs, converting them to clickable anchor tags with proper security attributes (target="_blank", rel="noopener noreferrer").
Why: Allows content creators to use natural markdown syntax while ensuring all external links open safely in new tabs.
Step 5 includes special logic to distinguish "context panels" (informational content) from "workflow steps" (actionable instructions). Only workflow steps get numbered, while context panels are displayed without step numbers.
Why: Provides clearer visual hierarchy and prevents confusion about which items require action vs. which are informational.
When AI enhancement fails, the error message is stored in progress state and displayed when the user returns to Step 3. This allows users to see what went wrong even after navigating away.
Why: Improves debugging experience and helps users understand why their prompt wasn't enhanced, without losing their PRD content.