Feature Specifications Document
HOAHelper — Complete Feature Specs
Comprehensive specifications covering all three user portals: Admin, Guard, and Resident. Scoped to the current React/Supabase/Edge Function stack. Ready for developer handoff.
Must-Have · Core v1.1 · Sprint 4 3 Portals · 12 Features Phase 1 · 6 weeks Stack: React + Supabase
01 Feature Overview
Admin Portal
Bylaw management, resident roster, visitor reports, review queue, and community setup wizard.
Guard Portal
DL barcode scanning, AI security check, Allow/Flag/Deny decisions, real-time activity feed.
Resident Chat
AI bylaw Q&A grounded in uploaded documents, persistent chat history, flagging for human review.
Problem Statement
HOA board members (volunteer, unpaid) spend 10+ hours/month answering repetitive resident questions about pool hours, pet policies, and parking rules. Gate guards have no consistent system for visitor decisions. Residents wait days for answers and have no self-service option. Existing HOA software (Buildium, TownSq) addresses accounting and maintenance — not Q&A or AI-guided security.
Success Criteria
  • 80%+ of routine resident questions answered without board intervention
  • Guard portal used for 100% of visitor entries (zero unlogged entries)
  • Admin setup completed in under 30 minutes by non-technical users
  • AI answer accuracy ≥ 95% on test document set (verified by manual QA)
  • Pilot-to-paid conversion rate ≥ 20% within 3-month trial window
Feature Category Breakdown
FeatureCategoryPriorityTarget ReleaseEffort
Admin setup wizardCoreMust-Havev1.0 ✓M
Bylaws upload + OCRCoreMust-Havev1.0 ✓L
Resident bylaw chatCoreMust-Havev1.0 ✓L
Guard portal + DL scanCoreMust-Havev1.0 ✓XL
AI security checkCoreMust-Havev1.0 ✓M
Visitor reports + CSVCoreMust-Havev1.0 ✓M
Review queue / flaggingCoreMust-Havev1.0 ✓S
SMS integration (Twilio)IntegrationShould-Havev1.1L
Multi-community (PM plan)EnhancementShould-Havev1.1XL
Magic link / email authEnhancementShould-Havev1.1S
Stripe billing integrationIntegrationMust-Havev1.1L
White-label brandingEnhancementCould-Havev1.2XL
02 User Personas & Use Cases
Primary — HOA Board Admin
Demographics
Age 45–65 · Volunteer · Moderate tech comfort · Uses iPhone · Gmail user
Goals
Reduce time spent on repetitive questions; have an audit trail for gate decisions; feel confident bylaws are being applied consistently
Pain Points
Gets texts on weekends; can't always remember the exact rule; frustrated that residents assume they're ignoring them
Usage Context
Weekly dashboard check · Monthly report review · Occasional bylaw update after board meetings
Secondary — Gate Guard
Demographics
Age 25–55 · Hourly staff · Low-to-medium tech comfort · Uses Android or assigned tablet
Goals
Make entry decisions quickly and confidently; avoid confrontation with visitors; have clear guidance for edge cases
Pain Points
Visitors claim they're allowed when they're not; no written policy to point to; slow during busy arrival windows
Usage Context
Every visitor arrival — 10–60 entries/day at a busy gate. Speed matters. Uses rear camera on phone for DL scanning.
Tertiary — Resident
Demographics
Age 30–70 · Homeowner · All tech comfort levels · Uses mobile browser primarily
Goals
Get a quick, accurate answer about what they're allowed to do; not have to wait days for a response or search through a PDF
Use Case
Typically evening or weekend usage. One-off questions about a planned modification, policy confirmation, or upcoming event.
B2B — Property Manager
Demographics
Age 30–55 · Licensed PM · High tech comfort · Manages 5–50 communities
Goals
Reduce escalations from HOA clients; offer AI tools as a differentiator; standardize gate procedures across all properties
Use Case
Manages multiple community accounts; needs white-label capability; evaluates tools at the org level, not the community level.
Edge Cases to Handle
  • Admin uploads a scanned PDF with <50 extractable characters — must trigger Tesseract OCR fallback automatically
  • Guard scans a non-US driver's license (international visitor) — AAMVA parser must fail gracefully and allow manual entry
  • Resident asks a question outside the scope of uploaded bylaws — AI must respond honestly and recommend contacting the board, not hallucinate
  • First-time admin with no staff row in DB (bootstrap case) — setup wizard must allow self-registration via the narrow bootstrap RLS policy
  • AI provider outage during a guard security check — UI must show a clear error and allow manual Allow/Flag/Deny without AI input
  • Community has zero bylaws uploaded — resident chat must show an informative empty state, not an AI error
03 Detailed User Stories
Epic: As an HOA community, we want an AI system that answers resident questions, guides guard decisions, and gives admins oversight — all grounded in our specific governing documents.
Story 1 — Bylaw Upload & AI Ingestion Must-Have
As an HOA admin,
I want to upload my community's PDF bylaws and have them immediately available to the AI,
so that residents can get accurate, document-grounded answers without me summarizing anything manually.
Acceptance Criteria
Givena real PDF with a text layer is uploaded (≤ 10MB)
Whenthe admin submits the upload form
Thentext is extracted via pdfjs-dist, appended to neighborhoods.bylaws, and the AI uses it for subsequent chat queries within 30 seconds

Givena scanned PDF (image-only, <50 extractable chars)
Whenupload is submitted
ThenTesseract OCR runs automatically, user sees a loading indicator with "Processing scanned document…", and text is appended on completion. User is not required to choose OCR manually.

Givena file exceeding 10MB or an unsupported type (e.g. .docx)
Whenfile is selected
Thena toast error appears before upload begins: "File must be PDF, TXT, or MD under 10MB." No upload attempt is made.
Story 2 — Guard DL Barcode Scan Must-Have
As a gate guard,
I want to scan a visitor's driver's license barcode and have the form auto-fill,
so that I can process entries quickly without errors from manual typing.
Acceptance Criteria
Givena US driver's license with a valid PDF417 barcode is presented
When"Scan DL" is tapped and the rear camera captures the barcode
Thenfirst name, middle name, last name, DOB (formatted MM/DD/YYYY), DL state, and DL number auto-fill within 3 seconds. Scanner modal closes automatically.

Givena non-US or unreadable DL is presented
Whenthe scanner fails to decode after 10 seconds
Thenan inline message reads "Could not read barcode — please enter visitor details manually." All form fields remain editable. No data is lost.

GivenDL scan is complete
Whenguard reviews pre-filled form
Thenall auto-filled fields are editable before submission. No field is locked.
Story 3 — Resident Bylaw Chat (Citation Required) Must-Have
As a resident,
I want to ask questions about community rules in plain language,
so that I get an accurate answer fast, without emailing the board and waiting days.
Acceptance Criteria
Givenbylaws are uploaded and the resident is signed in
Whena question is submitted
Thena response appears within 5 seconds, written in plain language, citing the specific section of the bylaws where the answer originates (e.g., "Pool Rules · Section 2.1")

Givena question whose answer is not in the uploaded bylaws
Whenthe AI processes the query
Thenthe response clearly states it could not find information in the community bylaws and recommends contacting the board directly. It does not speculate or provide generic HOA advice.

Givena question that is sensitive, legally complex, or emotionally charged
Whenthe AI generates a response
Thenthe message is flagged (needs_review = true in chat_messages) and appears in the admin review queue within 60 seconds
Story 4 — Stripe Billing & Trial Enforcement (v1.1) Should-Have
As an HOAHelper operator,
I want billing handled automatically via Stripe,
so that trials convert to paid subscriptions without manual intervention and revenue is captured reliably.
Acceptance Criteria
Givena new admin completes setup
Whenthey select the $49 trial at checkout
Thena Stripe Checkout session is created, payment is collected, and a trial_ends_at timestamp is written to the neighborhoods table. All features remain active during the trial period.

Giventhe trial_ends_at date has passed and no active subscription exists
Whenany user from that neighborhood accesses any portal
Thenthey see a paywall screen with upgrade options. No portal features are accessible. Admin can reactivate via Stripe checkout without contacting support.
04 Functional Requirements
F-01 · Bylaw Text Extraction
Input
File object (PDF / TXT / MD) from file input element. Max size: 10MB.
Processing
1. Check MIME type client-side before upload. 2. If PDF: attempt pdfjs-dist text extraction. 3. If extracted text < 50 chars: fall back to Tesseract.js OCR (loaded from unpkg CDN). 4. If TXT/MD: use File.text(). 5. Prefix output with "--- filename.pdf ---" and append to neighborhoods.bylaws.
Output
Updated neighborhoods.bylaws field (PATCH). Toast success: "Document added to your community bylaws." File listed in Governing Documents section with name and size.
Validation
File type: PDF | TXT | MD only. File size: ≤ 10MB. Extracted text must not be empty after trim() before appending.
F-02 · Guard Entry Logging
Input
Visitor form fields: first_name, middle_name, last_name, dob, dl_state, dl_number, vehicle_type, license_plate, destination. Decision: 'allowed' | 'flagged' | 'denied'. Optional: AI notes from security check.
Processing
Validate all required fields via Zod schema. POST to visitor_logs with guard_id = auth.uid(), neighborhood_id from current_neighborhood_id(). Write timestamp server-side.
Output
New row in visitor_logs. Realtime subscription broadcasts update to Recent Activity feed. Allow/Flag/Deny buttons disable post-submission. Form resets for next entry after 2 seconds.
Edge Cases
If AI security check fails (network error): guard can still log entry manually. Decision buttons remain enabled. Error toast: "AI check unavailable — proceeding with manual decision."
F-03 · AI Chat (task: chat)
Input
User message string. Conversation history (last 10 messages from chat_messages). neighborhood.bylaws (full text as system context). Provider: ANTHROPIC | OPENAI | GEMINI (env var).
Processing
Edge function validates JWT → looks up user's neighborhood → constructs provider-specific request with bylaws as system prompt → streams or awaits response → writes to chat_messages (role: 'user' and 'assistant'). Evaluates response for flagging heuristics.
Output
AI message rendered in ChatInterface. Persisted to chat_messages with created_at. If flagged: needs_review = true, visible in ReviewQueue.
Business Rules
AI must only reference uploaded bylaws — system prompt must explicitly instruct this. Response must include a citation (document name + section) when quoting a rule. AI must not provide legal advice or invent rules not in documents.
F-04 · AI Security Check (task: analyze_visitor)
Input
Visitor object (name, DOB, DL info, vehicle, destination). neighborhood.entry_rules (text). Structured output schema: { status: 'allowed'|'denied'|'flagged', notes: string }.
Output
Parsed JSON { status, notes }. UI pre-selects corresponding decision button. Notes displayed below buttons as AI reasoning. Guard can override the AI recommendation — their manual decision is what's logged.
Business Rules
  • BR-01: A user may only belong to one neighborhood at a time (no multi-community for residents/guards). Property Manager plan breaks this — deferred to v1.2.
  • BR-02: Only users with role='admin' for a neighborhood may modify bylaws, residents, or entry rules. RLS enforced at DB level via current_role().
  • BR-03: Guards (role='guard') may only INSERT to visitor_logs for their own neighborhood. They may not read chat_messages or modify bylaws.
  • BR-04: AI provider keys must never reach the browser bundle. All AI calls route through the Supabase Edge Function.
  • BR-05: The bootstrap policy (neighborhoods_insert_self) must self-destruct after first use — once a staff row exists for auth.uid(), the policy no longer grants access.
  • BR-06: Flagged chat_messages (needs_review=true) must be surfaced in ReviewQueue within 60 seconds via Supabase Realtime.
05 Non-Functional Requirements
Performance Targets
Chat AI response< 5s p95
Guard AI security check< 4s p95
DL scan to form fill< 3s
PDF text extraction< 10s
OCR (Tesseract)< 60s
Page initial load (FCP)< 2.5s
Dashboard data load< 1.5s
Security Requirements
Auth methodGoogle OAuth (required)
Magic link (v1.1)Email OTP via Supabase
API key exposureZero — Edge Fn only
DB accessRLS on all 5 tables
CSP policyStrict (self + Supabase + unpkg)
Audit logAll visitor_logs entries
Data isolationneighborhood_id on every row
Usability Requirements
WCAG levelAA (target)
Mobile supportiOS 16+ · Android 12+
Desktop browsersChrome 110+ · Safari 16+ · FF 110+
Screen sizes320px → 1440px
Guard UI font≥ 16px tap targets ≥ 44px
Error messagesPlain language, actionable
Setup time< 30 min for non-technical admin
06 Technical Specifications
Stack Reference
LayerTechnologyVersionNotes
FrontendReact + TypeScript + Vite19 / 5 / 6SPA, hosted on Vercel/Netlify/CF Pages
StylingTailwind CSS v44.xNo inline styles in production
Iconslucide-reactlatestNo Font Awesome — CSP violation risk
DatabaseSupabase Postgres + RLSPostgres 155 tables, all RLS gated
AuthSupabase Auth — Google OAuthMagic link deferred to v1.1
StorageSupabase Storagehoa-documents bucket, per neighborhood_id
AIEdge Function — provider agnosticDenoAnthropic | OpenAI | Gemini via env var
PDF parsingpdfjs-dist (legacy build)3.xSafari: manual ReadableStream drain required
OCRTesseract.js (unpkg CDN)5.xFallback only; self-host in public/ for zero CDN dep
DL scanhtml5-qrcode · PDF4172.x15fps, rear camera, 320×160 scan box
ValidationZod3.xAll form boundaries
TestingVitest + Testing Libraryjsdom environment
API Endpoints — Supabase Edge Function (ai)
POST /functions/v1/ai task: "chat" — Resident bylaw Q&A
{
"task": "chat",
"messages": Message[], // { role: 'user'|'assistant', content: string }[]
"neighborhoodId": "uuid" // resolved server-side from JWT
}
{
"content": "The pool is open 8 AM–8 PM, six days a week. Closed Tuesdays.\n\nSource: Pool Rules · Section 2.1",
"flagged": false
}
  • JWT must be valid — validated via supabase.auth.getUser(). 401 if missing or expired.
  • messages array must not be empty. 400 if missing.
  • System prompt must include full neighborhood.bylaws text. If empty: return "No bylaws uploaded yet" message without calling AI provider.
POST /functions/v1/ai task: "analyze_visitor" — Guard security check
{
"task": "analyze_visitor",
"visitor": {
"firstName": "string", "lastName": "string",
"dob": "YYYY-MM-DD", "dlState": "TX",
"destination": "123 Oak Lane", "vehicleType": "SUV"
},
"entryRules": "string"
}
{
"status": "allowed", // "allowed" | "denied" | "flagged"
"notes": "Visitor destination matches resident address. No flags on record."
}
Database Schema — Key Tables
-- neighborhoods
id uuid PRIMARY KEY DEFAULT gen_random_uuid()
name text NOT NULL
bylaws text DEFAULT ''
entry_rules text DEFAULT ''
created_by uuid REFERENCES auth.users(id)
trial_ends_at timestamptz -- v1.1: Stripe billing
stripe_customer_id text -- v1.1

-- visitor_logs
id uuid PRIMARY KEY
neighborhood_id uuid REFERENCES neighborhoods(id)
guard_id uuid REFERENCES auth.users(id)
first_name text
middle_name text
last_name text
dob date
dl_state text
dl_number text
vehicle_type text
license_plate text
destination text
decision text CHECK (decision IN ('allowed','flagged','denied'))
ai_notes text
created_at timestamptz DEFAULT now()
v1.1 Migration: Add trial_ends_at (timestamptz) and stripe_customer_id (text) columns to neighborhoods. Add subscription_status (text) CHECK ('trialing'|'active'|'past_due'|'canceled'). All nullable — existing rows treated as active until migration is applied.
07 UI Specifications
ComponentFileKey StatesMobile Notes
AdminDashboardcomponents/AdminDashboard.tsxLoading → Stats loaded → ErrorTiles wrap to 2-col at <640px
Onboarding (Manage Bylaws)components/Onboarding.tsxEmpty → Uploading → OCR → SavedFile input requires full-width tap target
GuardInterfacecomponents/GuardInterface.tsxIdle → Scanning → Form filled → AI check → Decision loggedAll buttons ≥ 44px. Decision buttons full-width on mobile.
ChatInterfacecomponents/ChatInterface.tsxEmpty → Message sent → Streaming → FlaggedInput pinned to bottom. Safe area inset on iOS.
ReportViewercomponents/ReportViewer.tsxDate tabs → Entries loaded → CSV exportStats in 2-col grid. Entry cards stacked.
ReviewQueuecomponents/ReviewQueue.tsxEmpty state → Items loaded → ReviewedSingle column, full-width cards.
Landing (marketing)components/Landing.tsxStatic → Sign In CTAHero stacks vertically. CTA full-width.
Loading State Standards
  • AI chat: show animated typing indicator (3 dots) in a message bubble while awaiting response
  • DL scan: "Scanning… point camera at the barcode on the back of the license"
  • OCR: "Processing scanned document… this may take up to 60 seconds"
  • Dashboard stats: skeleton cards (stone-100 background, no text)
  • All loading states must show within 200ms of action
Error State Standards
  • All errors surface via useToast() — never silent failures
  • Toast duration: errors = 6s, success = 3s
  • Error messages use errorMessage() normalizer from lib/errors.ts
  • Network errors: "Connection error — please try again"
  • Auth errors: redirect to /login, preserve intended destination
  • RLS violations: "You don't have permission to do that" — never expose Postgres error text to user
08 Testing Requirements
Unit Test Coverage Targets
  • lib/aamva.ts — AAMVA parser: 8 existing specs. Add: Canadian DL format, missing fields, malformed input
  • lib/extractText.ts — Mock pdfjs; test OCR fallback trigger (<50 chars); test .txt and .md paths
  • lib/validation.ts — All Zod schemas. Test boundary values and type coercion
  • lib/errors.ts — errorMessage() with Error object, string, Supabase error object, unknown
  • Overall branch coverage target: ≥ 80% on lib/
Integration Test Requirements
  • Edge function /ai — mock AI provider; test chat and analyze_visitor tasks
  • Edge function /ai — test JWT validation (missing, expired, valid)
  • Supabase RLS — test admin vs guard vs resident access on each table
  • Google OAuth redirect — verify Supabase redirect URL config in test env
  • Realtime subscription — visitor_logs INSERT triggers Recent Activity update
User Acceptance Test Scenarios
UAT-01 · Admin Onboards a New Community (Happy Path)
1
New user signs in via Google OAuth for the first time
2
Prompted to create neighborhood → enters name "Pinecrest Heights" → submits
3
Navigates to Manage Bylaws → uploads "pool_rules.pdf" (real PDF with text layer)
4
Sees "pool_rules.pdf · 115.8 KB" in Governing Documents list
5
Opens Test Chat → asks "When is the pool open?"
6
Receives accurate answer citing "Pool Rules · Section 2.1" within 5 seconds
Expected: All 6 steps complete without errors. Total time ≤ 30 minutes for a non-technical admin.
UAT-02 · Guard Logs a Visitor Entry via DL Scan (Mobile)
1
Guard opens Guard Portal on iPhone 14 (Chrome or Safari)
2
Taps "Scan DL" → camera opens with PDF417 scan box
3
Holds a US driver's license barcode in frame → form auto-fills within 3 seconds
4
Enters vehicle type and destination manually → taps "Run AI Security Check"
5
AI returns recommendation within 4 seconds → guard taps "ALLOWED"
6
Entry appears in Recent Activity feed. Admin sees it in Daily Reports.
Expected: Entry logged in visitor_logs with all fields. Realtime feed updates without page refresh.
UAT-03 · Out-of-Scope Question Handling
1
Resident opens bylaw chat
2
Asks "Can I dispute my HOA fees in court?"
3
AI responds that it cannot find this in the community bylaws and recommends contacting the board or a legal professional
4
Response is flagged in admin review queue (needs_review=true)
Expected: No invented legal advice. Clear referral. Message appears in ReviewQueue within 60 seconds.
09 Implementation Plan
1
Phase 1 — Stripe Billing Integration
8–10 days · Blocking for monetization
  • Add trial_ends_at and stripe_customer_id columns to neighborhoods (migration)
  • Build Stripe Checkout Edge Function for $49 trial and $9/mo subscription
  • Build Stripe webhook handler (checkout.session.completed, invoice.payment_failed, customer.subscription.deleted)
  • Add paywall middleware — check subscription_status on portal load, redirect to upgrade if past_due or canceled
  • Upgrade/billing UI for admin (current plan, next billing date, cancel flow)
  • Test with Stripe test mode across all webhook events
2
Phase 2 — Magic Link / Email Auth
3–4 days · Parallel with Phase 1
  • Enable Supabase magic link (OTP via email) in Auth settings
  • Add email input + OTP verification UI to Login.tsx
  • Handle both OAuth and OTP session flows in App.tsx auth state
  • Add "or continue with email" separator below Google button
  • Test: new user via email, existing user via email, cross-provider login attempt
3
Phase 3 — Favicon, OG Images, PWA Manifest
2–3 days · Low dependency
  • Create favicon.svg (emerald square + white home icon) → add <link rel="icon"> to index.html
  • Generate apple-touch-icon.png (180×180) for iOS home screen install
  • Create manifest.json with name, short_name, theme_color (#047857), icons array
  • Create OG image (1200×628) — static emerald/stone design with logo + tagline
  • Add og:image, og:title, og:description, twitter:card meta tags to index.html
  • Verify with Safari (favicon.ico request), Facebook OG debugger, Twitter Card validator
4
Phase 4 — Test Coverage Uplift
5–6 days · Can run parallel with Phase 2–3
  • Add Vitest coverage report (vitest --coverage) — identify gaps
  • Write unit tests for extractText.ts (pdfjs mock, OCR fallback, file type routing)
  • Add edge case specs to aamva.test.ts (Canadian format, missing fields)
  • Component tests: GuardInterface scan flow, ChatInterface flagging, ReviewQueue rendering
  • Integration test: Edge function /ai with mocked provider (Anthropic + OpenAI paths)
  • Target: ≥ 80% branch coverage on lib/, ≥ 60% on components/
Risk Assessment
Risk
Stripe webhook reliability — missed events could leave subscriptions in wrong state
Mitigation
Implement idempotency keys. Store raw webhook payload. Add Stripe dashboard monitoring. Manual resolution UI for admins.
Risk
Tesseract OCR loaded from unpkg.com — CDN outage breaks scanned PDF support
Mitigation
Self-host Tesseract assets in public/tesseract/. Update paths in extractText.ts. Zero external dependency. One-time effort.
Risk
PDF417 scan unreliable on desktop webcams (documented limitation)
Mitigation
UI copy says "works best on a phone camera." Manual entry always available as fallback. No engineering fix needed — expectation management.
10 Success Metrics & Monitoring
80%
Target auto-resolution rate for resident questions
< $100
Target customer acquisition cost
20%
Pilot-to-paid conversion target (3-month trial)
< 5%
Monthly churn rate target
Feature Adoption
Communities onboardedTrack weeklyTRACK
Bylaws upload rate≥ 80% of signupsWATCH
Resident chat DAU≥ 3 per communityTRACK
Guard portal entries/day≥ 5 per gated communityTRACK
Review queue review rate≥ 70% reviewed within 24hWATCH
Setup completion rate≥ 90% complete setupALERT if <75%
Technical Performance
AI p95 response time< 5sALERT if >8s
Edge function error rate< 1%ALERT if >3%
DL scan success rate≥ 85% (mobile)WATCH
OCR completion rate≥ 90%TRACK
Supabase RLS errors0 unintended denialsALERT any
Stripe webhook success≥ 99.5%ALERT if <98%
Business Impact
Trial-to-paid conversion≥ 20%ALERT if <10%
Monthly churn< 5%ALERT if >8%
Support tickets/communityDeclining trendTRACK
NPS score≥ 40QUARTERLY
MRR growth≥ 15% MoM (seed)WATCH
500 paid subs target18 months from launchMILESTONE
Monitoring Setup — Recommended Stack
WhatToolNotes
Edge function errors + latencySupabase Dashboard → FunctionsBuilt-in logs; set up alerts for error rate > 3%
Frontend errors + performanceSentry (free tier)Add @sentry/react to capture uncaught errors and Core Web Vitals
User analytics + feature adoptionPostHog (self-host or cloud)Track: bylaw upload, chat message sent, DL scan, entry logged, review queue action
Stripe eventsStripe Dashboard + webhooksEnable "Failed to deliver" alerts; log all events to a stripe_events table
Uptime monitoringBetter Uptime (free tier)Monitor hoahelper.app and Supabase project URL; 1-minute check interval
User feedbackIn-app NPS widget (Delighted or Hotjar)Trigger 7 days after first resident chat message