Investor Interest Signal Strength
Overview
The Investor Interest Signal detects when a viewer is engaging with a pitch deck beyond a casual glance. It uses a combination of behavioral signals derived from existing analytics data. Signals are shown only to the founder — viewers never see them.
Signal Rules
| Signal | Rule | Label |
|---|---|---|
| Revisited | Visitor viewed the deck on ≥ 2 distinct calendar days | Revisited |
| Multiple views | Visitor has ≥ 3 total slide view records | Viewed multiple times |
| Deep read | Visitor spent ≥ 20 seconds on ≥ 2 different slides | Spent time on key slides |
| Quick return | Visitor returned within 3 days of their first visit | Returned quickly |
| Long session | Visitor's total time across all slides ≥ 60 seconds | Extended viewing |
Architecture
Data Flow
sequenceDiagram
participant V as Investor (Viewer)
participant App as ImageDeckViewer
participant DB as Supabase (deck_page_views)
participant F as Founder Dashboard
V->>App: Views slide for 25s
App->>DB: record_deck_visit(deck, page, total_time, is_unique)
DB-->>DB: Atomic stats increment + page view insert
Note over V,DB: Investor returns 2 days later
V->>App: Views slides again
App->>DB: syncSlideStats(deck, page, 15)
DB-->>DB: INSERT new row (new 24h window)
F->>DB: getVisitorSignals(deckId)
DB-->>F: Grouped rows by visitor_id
F->>F: Compute signals client-side
F->>F: Render badges on DeckAnalytics pageDatabase Schema Change
-- Two column additions to existing table
ALTER TABLE deck_page_views
ADD COLUMN IF NOT EXISTS time_spent REAL DEFAULT 0;
ALTER TABLE deck_page_views
ADD COLUMN IF NOT EXISTS viewer_email TEXT;The deck_page_views table now stores:
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
deck_id | UUID | Foreign key to decks |
page_number | INT | Slide number viewed |
visitor_id | TEXT | Persistent anonymous visitor ID |
viewer_email | TEXT | Email captured from AccessGate (nullable) |
viewed_at | TIMESTAMP | When the view occurred |
time_spent | REAL | Seconds spent on this slide by this visitor |
country | TEXT | Visitor's country (via Vercel Edge) |
city | TEXT | Visitor's city (via Vercel Edge) |
country_code | TEXT | ISO country code (e.g., "US") |
Signal Computation
Signals are computed client-side, not stored as flags. This keeps the system maintainable, avoids stale data, and allows signal thresholds to be tweaked without migrations.
NOTE
Broadened Scope: Queries now include views where data_room_id is NULL. This allows for "Historical Affinity" tracking — if a visitor views a deck independently and then later views it inside a private Data Room, their full engagement history is combined to calculate the interest signal.
deck_page_views (grouped by visitor_id)
│
▼
interestSignalService.getVisitorSignals(deckId) OR getRoomVisitorSignals(roomId)
│
├──► filter(Boolean) + empty-guard → Prevent malformed queries
├──► Include null data_room_id → Capture historical affinity
├──► Count distinct viewed_at dates → "Revisited"
├──► Count total rows → "Viewed multiple times"
├──► Filter slides with time_spent≥20 → "Spent time on key slides"
├──► Compare first/last visit dates → "Returned quickly"
└──► Sum all time_spent → "Extended viewing"
│
▼
VisitorSignal[] (sorted by signal count)File Map
| File | Role |
|---|---|
src/services/analyticsService.ts | Modified — syncSlideStats() now uses record_deck_visit RPC for secure, atomic updates. |
src/services/interestSignalService.ts | New — queries deck_page_views, computes signals + slide breakdown per deck or room |
src/components/dashboard/InterestSignalBadge.tsx | New — color-coded neutral badge component |
src/pages/DeckAnalytics.tsx | Modified — "Visitor Engagement Signals" section with expandable slide time charts |
src/pages/DataRoomDetail.tsx | Modified — "Visitor Signals" section aggregated across all assets in the room |
src/components/dashboard/TopDecksCard.tsx | Modified — shows "N interested viewers" hint per deck |
src/pages/Viewer.tsx | Modified — stores captured email, passes to ImageDeckViewer |
src/components/ImageDeckViewer.tsx | Modified — accepts viewerEmail prop, passes to syncSlideStats |
UI Design
- DeckAnalytics page: A
DashboardCardsection titled "Visitor Engagement Signals" lists each visitor (email or anonymous) with their signal badges - Expandable slide breakdown: Click a visitor card to reveal a vertical bar chart showing per-slide time spent
- TopDecksCard: Subtle "N interested viewer(s)" text below deck titles
- Badge styles: Each signal type has a unique color — blue (revisit), indigo (multiple views), green (deep read), amber (quick return), violet (extended viewing)
- Language: All labels use neutral, factual language (no "hot lead" or "high interest")
- Email display: Shows actual email when captured (e.g.
john@acme.com), falls back to "Visitor #N" for anonymous viewers
Future Enhancements
- [x] Email-based visitor identity (when Require Email is enabled)
- [x] Per-visitor slide time breakdown (expandable vertical bar chart)
- [ ] "Viewed updated version" signal
- [ ] Push notifications to founder when a new signal triggers
- [ ] Signal strength score (weighted combination of all signals)
