• Townie
    AI
  • Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
lightweight

lightweight

glimpsePico

Remix of lightweight/glimpse2-runbook-view-glimpse-save-login
Public
Like
glimpsePico
Home
Code
9
_townie
13
backend
8
frontend
9
shared
3
.vtignore
AGENTS.md
README.md
deno.json
H
main.tsx
Branches
2
Pull requests
Remixes
1
History
Environment variables
9
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
…
README.md

Glance Demos

A Val Town application for managing and viewing demos with authentication.

Features

  • Authentication: Google OAuth via LastLogin
  • Dashboard: User-friendly interface showing system status
  • Health Monitoring: Real-time system health checks
  • Modular Architecture: Clean separation of concerns

Authentication

This application uses LastLogin for authentication:

  • Login: Users sign in with their Google account
  • Protected Routes: All routes require authentication except public endpoints
  • Logout: Users can logout via /auth/logout (handled automatically by LastLogin)
  • Session Management: Automatic session validation and renewal

Public Routes

The following routes are accessible without authentication:

  • /api/health - System health status

Protected Routes

Routes are protected by different authentication mechanisms:

User Authentication (Google OAuth via LastLogin):

  • / - Main dashboard (shows user info and system status)
  • /api/* - API endpoints (except health)
  • /views/* - View routes including /views/glimpse/:id
  • /glimpse/* - Shortcut routes including /glimpse/:id (equivalent to /views/glimpse/:id)

Webhook Authentication (X-API-KEY header):

  • /tasks/* - Webhook endpoints for external integrations (POST requests only)
    • GET requests to /tasks/* are allowed without authentication for debug endpoints

Project Structure

├── backend/
│   ├── controllers/         # Business logic controllers
│   ├── routes/             # Route definitions and HTTP handling
│   │   ├── api/            # API endpoints
│   │   ├── glimpse/        # Glimpse routes (enhanced with React frontend)
│   │   ├── tasks/          # Task-related routes
│   │   ├── views/          # User-facing views
│   │   └── authCheck.ts    # Authentication middleware
│   └── services/           # External service integrations
├── frontend/               # React frontend assets
│   ├── glimpse.html        # HTML template for glimpse views
│   ├── glimpse.tsx         # React entry point
│   ├── components/         # React components
│   │   ├── GlimpseView.tsx # Main glimpse display component
│   │   ├── NotionBlock.tsx # Notion block renderer
│   │   └── NotionProperty.tsx # Property display component
│   └── README.md
├── shared/                 # Shared utilities and types
│   ├── types.ts           # TypeScript interfaces for Notion data
│   ├── utils.ts           # Shared utility functions
│   └── README.md
└── main.tsx               # Application entry point with static file serving

Architecture

The application follows a clean MVC architecture with proper separation of concerns:

Route Layer (HTTP Handling)

  • Handles HTTP request/response formatting
  • Extracts parameters from requests
  • Applies authentication middleware
  • Calls controller functions and formats responses
  • Manages HTTP status codes and error responses

Controller Layer (Business Logic)

  • Contains business logic and orchestrates service calls
  • Returns plain data objects (not HTTP responses)
  • Handles data validation and transformation
  • Filters sensitive data (e.g., button properties)
  • Provides consistent success/error response structure

Service Layer (External Integrations)

  • Handles external API calls (Notion, databases)
  • Manages data persistence
  • Returns structured results with success/error information

Response Format

All controller functions return a consistent structure:

{ success: boolean, data: any | null, error: string | null, details?: string // Additional error context }

Routes then format these into appropriate HTTP responses.

Glimpse Routes

The application provides multiple routes for accessing page data and user authentication:

Authentication Routes

  • GET /glimpse/login - User-specific login redirect

    • Requires user authentication (Google OAuth via LastLogin)
    • Looks up authenticated user's email in GLANCE_DEMOS_DB_ID database
    • If user found: Redirects to user's personal path (from Path property)
    • If user not found: Creates new user record and redirects to /glimpse/thanks
    • Shows detailed error information for debugging database structure issues
  • GET /glimpse/thanks - New user welcome page

    • Shows confirmation that user account was created
    • Explains next steps (admin review, email with demo link)
    • Provides timeline expectations (1-2 business days)

Data Access Routes

  • GET /views/glimpse/:id - Get complete page data with blocks by Notion page ID (JSON only)
  • GET /glimpse/:id - Enhanced with React Frontend - Content negotiation based on Accept header:
    • Browser requests (Accept: text/html): Returns rich React frontend with interactive Notion content display
    • API requests (Accept: application/json): Returns raw JSON data (same as before)
    • Fallback: If HTML template fails to load, automatically serves JSON

React Frontend Features

The /glimpse/:id endpoint now includes a rich React frontend when accessed via browser:

Content Rendering

  • Rich Notion Blocks: Supports headings, paragraphs, lists, code blocks, callouts, images, videos, tables, and more
  • Enhanced Code Blocks: Syntax highlighting powered by Prism.js with support for 30+ languages, line numbers, and language indicators (optimized for performance with non-blocking JavaScript loading)
  • Property Display: Shows all page properties with type-specific formatting and icons
  • Responsive Design: Mobile-friendly layout using TailwindCSS
  • Server-Side Data Injection: Initial data injected to eliminate loading states

User Experience

  • Error Handling: Graceful error states with retry functionality
  • Loading States: Smooth loading indicators
  • Navigation: Easy return to dashboard
  • Debug Mode: Raw data view in development environments
  • Dynamic Remote Support: Remote support section appears only when agents are assigned to the page

Agent Data Integration

  • Real-time Agent Updates: Fetches agent data every 5 seconds when user is authorized
  • Conditional Display: Remote support section only appears when agents array has at least one item
  • Complete Agent Information: Shows all blob contents including pageId, agents array, lastUpdated, and assignedAt timestamps
  • Authorization-based: Only fetches agent data for authorized users (matching email addresses)
  • Error Handling: Silent console logging for missing agent data without disrupting user experience

Technical Implementation

  • Content Negotiation: Single endpoint serves both HTML and JSON
  • Static File Serving: Frontend assets served via /frontend/* route
  • React 18.2.0: Pinned version for consistency
  • TypeScript Support: Shared types for Notion data structures
  • Prism.js Integration: Advanced syntax highlighting with automatic language detection, line numbers, and support for 30+ programming languages including JavaScript, TypeScript, Python, Java, C++, and more (performance-optimized with non-blocking script loading)

Note: The glimpse endpoints now provide both programmatic access (JSON) and user-friendly viewing (React frontend) from the same URL, maintaining backward compatibility while adding rich content display capabilities.

Demo API Endpoints

API endpoints for accessing Notion page data with different levels of detail:

  • GET /api/demo/:id/properties - Returns page properties only
  • GET /api/demo/:id - Returns page properties + all blocks recursively
  • GET /api/agent/:id - Returns agent blob data for a specific page ID

Architecture:

  • Routes: Handle HTTP concerns (parameter extraction, response formatting, status codes)
  • Controllers: Contain business logic (getDemoProperties, getDemoFull)
  • Services: Handle Notion API integration

Authentication Behavior:

  • Browser requests: Require user authentication (Google OAuth via LastLogin)
  • Internal requests: Bypass authentication when called from within the Val (identified by Deno user agent)

Response Format: Routes return the data directly from controllers on success:

{ // Notion page object with properties // For full endpoint: also includes "blocks" array with recursive block data }

On error, routes return:

{ "error": "Error message", "details": "Additional error context" }

Usage Examples:

// Internal call from within Val (no authentication needed) const response = await fetch('/api/demo/page-id/properties'); const data = await response.json(); // External browser request (requires authentication) // User must be logged in via Google OAuth

All glimpse routes:

  • Require user authentication
  • Return complete page data including properties and blocks recursively
  • Filter out button properties from Notion page data
  • Return standardized JSON responses (except authentication routes which redirect or show HTML)
  • Use the same controller functions as the API endpoints for consistency

New User Registration Flow

  1. Authentication: User must be authenticated via Google OAuth (handled by LastLogin)
  2. Database Lookup: System queries GLANCE_DEMOS_DB_ID database for user's email
  3. User Creation: If not found, creates new user record with email address
  4. Welcome Page: Redirects to /glimpse/thanks with next steps information
  5. Admin Process: Admin reviews new users and adds demo URLs manually
  6. User Return: User can return to /glimpse/login once URL is configured

Database Requirements for Login

The GLANCE_DEMOS_DB_ID database must contain:

  • Email property: Contains user's email address (exact match with authenticated email)
  • Path property: Contains user's redirect path in format /glimpse/:id (optional for new users)

Supported Path property types: rich_text, title, url

Error Handling

The login endpoint provides detailed error information for debugging:

  • Missing environment variables
  • Database query failures
  • User creation failures (falls back to access denied page)
  • Invalid or missing Path properties
  • Path format validation errors (must be /glimpse/:id)

The dashboard displays both routes in a comparison table for easy testing.

Development

The application is built with:

  • Hono: Web framework for routing and middleware
  • LastLogin: Authentication service
  • TypeScript: Type-safe development
  • Val Town: Hosting platform

Webhook Authentication

The application supports webhook endpoints for external integrations (like Notion webhooks):

Configuration

Set the webhook secret in your environment:

NOTION_WEBHOOK_SECRET=your-secret-key-here

Webhook Endpoints

  • POST /tasks/notion-webhook - Main webhook endpoint for Notion integrations (requires X-API-KEY header)
  • POST /tasks/url - Updates Notion page URL property with glimpse URL (requires X-API-KEY header)
  • POST /tasks/assign - Assigns agents to tasks based on Assigned property matching (requires X-API-KEY header)
  • POST /tasks/visitor/email/link - Sends demo link email to visitor based on Email and URL properties (requires X-API-KEY header)
    • Dynamic Host Support: Uses the request host to construct email links instead of hardcoded URLs
    • Custom Domain Ready: Email links automatically match the domain used to access the webhook
    • Professional Branding: Maintains consistent domain experience across all user touchpoints
  • POST /tasks/visitor/email/thanks - Sends thank-you email to visitor based on Email property (requires X-API-KEY header)
    • Dynamic Host Support: Uses the request host for consistent branding
    • Agent Integration: Includes agent name and email for direct follow-up communication
    • Professional Follow-up: Maintains consistent domain experience for post-demo communication
  • POST /tasks/test - Test endpoint for webhook authentication (requires X-API-KEY header)
  • GET /tasks/debug-webhook - Debug endpoint to check webhook configuration

Task Assignment Webhook (/tasks/assign)

The assignment webhook automatically assigns agents to tasks based on Assigned property matching:

Workflow:

  1. Receives webhook with page ID from Notion
  2. Retrieves page properties to extract Assigned and Viewing properties
  3. Checks if Viewing property is true - if not, skips assignment and logs result
  4. Queries GLANCE_AGENTS_DB_ID database for agents with matching Assigned property
  5. STEP 1: Clear Current Demo Blob - Immediately clears the agent blob for this demo
  6. STEP 2: Find New Agents - Queries agents database by Assigned property
  7. STEP 3: Collect Agent Data - Fetches complete agent information and validates
  8. STEP 4: Clear Agents from Other Demo Blobs - Removes agents from any other demo blobs to prevent double-assignment
  9. STEP 5: Update Current Demo Blob - Stores new agent assignments in the current demo's blob

Blob-First Architecture:

  • Immediate Updates: Agent blob is updated immediately for fast frontend response
  • Multi-Blob Clearing: Ensures agents only appear in one demo blob at a time
  • Eventual Consistency: Cron jobs handle Notion database cleanup in the background
  • No Manual Unassignment: Assignments are cleared automatically when Viewing = false
  • Reliable: Blob operations are simpler and more reliable than complex relation management

Relation Management:

  • Simplified Approach: Only updates the demo page's "Glimpse agents" property
  • Automatic Bidirectional Updates: Notion automatically updates agents' "Glimpse demos" properties
  • No Manual Clearing: Eliminates the risk of clearing relations without repopulating them
  • Reliable Reassignment: Works correctly even when reassigning the same agents

Error Handling & Reliability:

  • Transactional Approach: Collects all required data before making any changes
  • Early Validation: Aborts assignment if any agent data cannot be fetched
  • Atomic Updates: Critical Notion updates happen together or not at all
  • Non-Blocking Blob Operations: Both blob updates and blob clearing are non-blocking
  • Comprehensive Logging: Detailed checkpoint logging at each phase for debugging
  • Graceful Degradation: Continues with available data when possible
  • Synchronized Blob Clearing: Removes agents from affected demo blobs when reassigning

Agent Blob Storage:

  • Key Pattern: {project}--agent--{pageId} (where project is derived from Val Town project title and pageId is from the webhook)
  • Data Structure:
{ "pageId": "page-id", "agents": [ { "agentId": "agent-page-id", "agentName": "Agent Name", "agentEmail": "agent@example.com", "agentMeetUrl": "https://meet.google.com/...", "agentPhone": "+1234567890" } ], "lastUpdated": "2025-09-10T19:52:00.000Z", "assignedAt": "2025-09-10T19:52:00.000Z" }

Requirements:

  • Page must have a Viewing property set to true (assignment only occurs for actively viewed pages)
  • Page must have an Assigned property with assigned person
  • Agents database must have pages with Assigned properties matching the assigned person
  • Original page must have a "Glimpse agents" relation property
  • Agent pages must have a "Glimpse demos" relation property

Viewing Property Support:

  • Checkbox: true value
  • Select: "true" option name
  • Rich Text: "true" or "yes" text content (case-insensitive)

Response Format (Assignment Completed):

{ "success": true, "message": "Task assignment completed successfully", "pageId": "page-id", "personId": "person-id", "agentsAssigned": 2, "agentsClearedCount": 1, "agentsSkippedClearing": 1, "agentBlobUpdated": true, "blobClearsAttempted": 2, "blobClearsSuccessful": 2, "timestamp": "2025-09-10T16:51:24.733Z" }

Response Format (Assignment Skipped):

{ "success": true, "message": "Page is not being viewed - assignment skipped", "pageId": "page-id", "viewing": false, "timestamp": "2025-09-10T16:51:24.733Z" }

Relation Management:

  • Smart Clearing: Only clears agents when they're assigned to different demos
  • Preserves Same-Demo Assignments: No clearing when reassigning to the same demo
  • Prevents Multiple Assignments: Ensures agents are only assigned to one demo at a time
  • Efficient: Minimal API calls - only clears when necessary
  • Reliable: Handles new agents, existing agents, and reassignments correctly

Automated Cleanup System

The application includes automated cleanup of stale data through dedicated cron jobs that use ALLOWED_HOSTS for project isolation:

Agent Cleanup Cron (cleanup-agent.ts)

Purpose: Automatically clears agent assignments for pages that no longer have active agents.

Schedule: Runs every minute (configurable via Val Town cron settings)

Host Scoping: Uses ALLOWED_HOSTS environment variable to only process agent blobs from legitimate webhook sources, preventing interference with other vals' data.

Workflow:

  1. Blob-First Scanning: Scans all agent blobs with key pattern glimpse--agent--*
  2. Empty Agent Detection: Identifies blobs where agents: [] (empty array)
  3. Assignment Clearing: For each empty agent blob, clears:
    • Assigned property: Removes assigned person from Notion
    • Agent blob: Deletes the empty blob from storage
  4. Comprehensive Logging: Detailed success/failure/skip reporting for monitoring

Blob-First Architecture:

  • Efficient Processing: Only processes blobs that exist, scales with actual usage
  • Accurate Detection: Clears assignments based on actual agent data, not viewing status
  • Storage Cleanup: Removes empty blobs to keep storage clean
  • Consistent Pattern: Matches the blob-first approach used by viewing cleanup

Performance Benefits:

  • Fast Execution: Blob scanning is faster than database queries
  • Targeted Processing: Only processes pages with empty agent arrays
  • Resource Efficient: Minimal API calls to Notion

Monitoring Output:

{ "success": true, "message": "Agent cleanup completed", "blobsProcessed": 5, "blobsSuccessful": 4, "blobsFailed": 0, "blobsSkipped": 1, "processingTime": "1.2s", "timestamp": "2025-09-10T22:15:00.000Z" }

This automated cleanup ensures that agent assignments are removed when no agents are actively assigned, maintaining clean data and preventing stale assignments from appearing in the frontend.

Viewing Cleanup Cron (cleanup-viewing.ts)

Purpose: Automatically cleans up stale viewing sessions that haven't received heartbeat updates.

Schedule: Runs every minute (configurable via Val Town cron settings)

Host Scoping: Uses ALLOWED_HOSTS environment variable to only process viewing blobs from legitimate sources, ensuring project isolation and preventing interference with other vals' viewing data.

Workflow:

  1. Host-Scoped Scanning: Scans viewing blobs only from hosts listed in ALLOWED_HOSTS
  2. Stale Session Detection: Identifies sessions where sessionStart is not null but lastUpdate is older than 60 seconds
  3. Session Cleanup: Sets sessionStart to null and updates lastUpdate timestamp
  4. Notion Sync: Updates "Session start" property to null in Notion
  5. Interaction Logging: Logs "Pageview ended" interactions for abandoned sessions
  6. Comprehensive Logging: Detailed success/failure reporting for monitoring

Security Benefits:

  • Project Isolation: Only processes blobs from configured webhook/viewing hosts
  • Account Safety: Prevents accidental modification of other vals' viewing sessions
  • Explicit Control: Clear boundaries for which viewing data this val manages

Both cleanup crons require the ALLOWED_HOSTS environment variable to maintain security and prevent cross-val interference in Val Town's account-level blob storage.

Authentication

Webhook endpoints require the X-API-KEY header:

curl -X POST https://your-val.web.val.run/tasks/test \ -H "X-API-KEY: your-secret-key-here"

Dynamic Host Support

The email service supports dynamic host detection for professional, branded email links:

How It Works

  • Request Host Detection: Extracts the host from incoming webhook requests (c.req.header("host"))
  • Dynamic URL Construction: Constructs email links using the request host instead of hardcoded URLs
  • Fallback Support: Uses original Notion URL if no request host is available

Benefits

  • Custom Domain Ready: Works automatically with any custom domain setup
  • Professional Branding: Email links match the domain users are familiar with
  • Consistent Experience: Maintains domain consistency across all user touchpoints
  • Future-Proof: No code changes needed when adding new domains

Example Flow

  1. User accesses app via demo.company.com
  2. Notion webhook triggered, sent to demo.company.com/tasks/visitor/email/link
  3. Handler extracts host: "demo.company.com"
  4. Email service creates link: https://demo.company.com/glimpse/abc123
  5. User receives professional, branded email link

Implementation

  • Email Service: sendVisitorDemoLink() accepts optional requestHost parameter
  • Webhook Handler: Extracts host from request and passes to email service
  • URL Construction: https://${requestHost}/glimpse/${pageId} when host provided
  • Sender Address: Remains static (username.project@valtown.email) for deliverability

Testing

Use the webhook testing form in the dashboard:

  1. Navigate to your dashboard at /
  2. Find the "Webhook Endpoint" section
  3. Enter your NOTION_WEBHOOK_SECRET value
  4. Click "Test Webhook" to verify authentication

Environment Variables

Configure these environment variables for full functionality:

  • GLANCE_DEMOS_DB_ID - Notion database ID for demos
  • GLANCE_CONTENT_DB_ID - Notion database ID for content
  • GLANCE_INTERACTIONS_DB_ID - Notion database ID for interactions
  • GLANCE_AGENTS_DB_ID - Notion database ID for agents
  • GLANCE_ENVIRONMENTS_DB_ID - Notion database ID for environment/subdomain tracking
  • NOTION_API_KEY - Notion API key for database access
  • NOTION_WEBHOOK_SECRET - Secret key for webhook authentication
  • ALLOWED_SUBDOMAINS - Comma-separated list of webhook subdomains for blob cleanup (e.g., glance,glancedev,glancestaging)

Project-Based Blob Keys

The application uses a project-based blob key system that derives prefixes from the Val Town project title:

Key Generation

  • Pattern: {project}--{type}--{id}
  • Project Prefix: Extracted from Val Town project title using parseVal(), sanitized to first word, lowercase, alphanumeric only
  • Consistency: Same prefix used for both blob keys and email addresses (username.{project}@valtown.email)
  • Example: For project "Glimpse2 Runbook View", keys use glimpse2--viewing--pageId and emails use username.glimpse2@valtown.email

Benefits

  • Multi-Project Support: Different Val Town projects can coexist without key conflicts
  • Consistent Branding: Blob keys and email addresses derive from same project identifier
  • Scalability: Easy to identify and manage data across different project deployments
  • Clean Architecture: Simple, predictable key generation without complex fallback logic

Implementation Status

✅ Complete - All blob operations (viewing, agent) now use project-based keys exclusively

Real-time Viewing Analytics

The application includes real-time viewing analytics with immediate Notion synchronization and email-based authorization:

Authorization & Security

  • Email-based Access Control: Viewing analytics are only tracked when the authenticated user's email matches the page's Email property
  • Frontend Authorization: Email comparison happens on the frontend before any API calls are made
  • Zero Unauthorized Calls: Users without matching emails generate no viewing API requests
  • Automatic Detection: System automatically extracts emails from Notion page properties and user authentication

Blob Storage + Notion Sync

  • Fast blob updates: Page viewing status stored in Val Town blob storage for instant response (~100ms)
  • Immediate Notion sync: When users start viewing pages, Notion database is updated immediately
  • Automatic cleanup: Cron job runs every minute to mark stale sessions (>1 minute old) as not viewing

Viewing Blob Storage

  • Key Pattern: {project}--viewing--{pageId} (where project is derived from Val Town project title and pageId is the Notion page ID)
  • Data Structure:
{ "pageId": "notion-page-id", "tabVisible": true, "lastUpdate": "2025-01-XX...", "userEmail": "user@example.com", "sessionId": "unique-session-id", "dateCreated": "2025-01-XX...", "sessionStart": "2025-01-XX...", "url": "https://example.com/page-path" }
  • sessionStart: Single source of truth for session state and timing
    • null = No active session (not viewing)
    • timestamp = Active session that started at this time (currently viewing)
  • dateCreated: Set only when the blob is first created, never updated thereafter (first time page was ever viewed)
  • lastUpdate: Updated on every viewing status change or heartbeat
  • sessionId: Unique identifier preserved across updates for the same viewing session

Session State Logic

  • Session Active: sessionStart !== null (user is currently viewing)
  • Session Inactive: sessionStart === null (user is not viewing)
  • No Contradictory States: Impossible to have viewing without session start time

Viewing Data Flow

  1. Authorization Check: Frontend compares user email with page Email property
  2. Authorized Users: Frontend calls /api/viewing → Blob updated → Interaction logged to GLANCE_INTERACTIONS_DB_ID
    • Session active (sessionStart has value): Creates interaction record with "Pageview started", session timestamp, page relation, and clean URL
    • Session inactive (sessionStart is null): No interaction logging (heartbeat only)
  3. User continues viewing: Frontend updates blob every 4 seconds (no Notion calls unless sessionStart changes)
  4. User leaves/session stale: Cron detects stale session → Blob updated (sessionStart cleared)
  5. Unauthorized Users: No API calls made, no viewing analytics tracked

Simplified Architecture

The viewing system now uses an event-driven architecture with a single source of truth:

  • Blob Storage: Fast response for viewing status updates
  • Interactions Database: Complete audit trail of all viewing events
  • Demos Database: Real-time status derived via Notion relations and rollups

Interaction Logging

Every time sessionStart is set or cleared in the viewing blob, interaction records are automatically created:

"Pageview started" interactions:

  • Name: "Pageview started"
  • Date: Session start timestamp from the blob
  • Glimpse demos: Relation to the viewed page
  • URL: Clean URL (origin + pathname, no query parameters) where the page was viewed
  • Subdomain: Subdomain where the viewing occurred

"Pageview ended" interactions:

  • Name: "Pageview ended"
  • Date: Session end timestamp (when session became stale)
  • Glimpse demos: Relation to the viewed page
  • URL: Clean URL where the page was viewed (preserved from session start)
  • Subdomain: Subdomain where the viewing occurred

This provides a complete chronological record of all page viewing activities with full context including URLs for both session start and end events.

Notion Property Behavior

"Session start" Property

  • Set when: User starts viewing a page (sessionStart changes from null to timestamp)
  • Value: Uses the sessionStart timestamp from the blob (when the current session began)
  • Cleared when: Session ends (sessionStart becomes null)
  • Primary status indicator: Has value = viewing, empty = not viewing

"Viewing start" Property

  • Set when: Page is viewed for the first time ever (blob dateCreated is set)
  • Value: Uses the dateCreated timestamp from the blob (first-ever view)
  • Never updated: Immutable historical record
  • Purpose: Analytics on page discovery patterns

Unified Session Model

  • Single Source of Truth: sessionStart property determines both session existence and timing
  • Logical Consistency: sessionStart !== null ↔ user is viewing
  • No Contradictions: Impossible to have viewing state without session start time
  • Simplified Logic: One property instead of separate viewing boolean and sessionStarted timestamp

Email Property Requirements

Pages must include an Email property in Notion for viewing analytics to work:

  • Property Name: "Email" or "email"
  • Property Type: Email type or Rich Text type
  • Content: Must exactly match the authenticated user's email address

Notion Database Schema

Pages in your Notion databases should include these properties for viewing analytics:

  • Email (Email or Rich Text) - Required for authorization
  • Interactions (Relation to GLANCE_INTERACTIONS_DB_ID) - Shows all viewing events
  • Latest Interaction (Rollup from Interactions) - Most recent interaction timestamp
  • Currently Viewing (Formula/Rollup) - Derived from recent interactions
  • First Viewed (Rollup from Interactions) - When page was first discovered

Interactions Database Schema

The GLANCE_INTERACTIONS_DB_ID database should include these properties:

  • Name (Title) - Interaction type (e.g., "Pageview started", "Pageview ended")
  • Date (Date) - When the interaction occurred
  • Glimpse demos (Relation) - Links to the viewed page
  • Subdomain (Rich Text) - Subdomain where the interaction occurred
  • URL (URL) - Clean URL where the page was viewed (automatically populated)

Real-Time Status via Relations

  • Immediate: Relation property shows linked interaction records as they're created
  • Near Real-Time: Rollup properties calculate current viewing status from interaction timestamps
  • Rich Context: Complete interaction history visible in the relation property

URL Tracking

The viewing analytics system automatically captures and stores clean URLs for comprehensive tracking:

Frontend URL Capture

  • Automatic: URLs are captured automatically when viewing sessions start
  • Clean URLs: Only origin + pathname are stored (query parameters and fragments are stripped)
  • Example: https://demo.company.com/glimpse/abc123 (not https://demo.company.com/glimpse/abc123?ref=email&utm_source=campaign)

Storage & Analytics

  • Blob Storage: URLs are stored in viewing blobs alongside other session data
  • Notion Integration: URLs are automatically logged to interaction records in the GLANCE_INTERACTIONS_DB_ID database
  • Privacy-First: No sensitive query parameters or tracking data is stored

Benefits

  • Complete Context: Know exactly where users are viewing pages
  • Multi-Domain Support: Track viewing across different domains and subdomains
  • Clean Analytics: URL data without sensitive or tracking parameters
  • Audit Trail: Full URL history in Notion for comprehensive reporting

Frontend Integration

  • Email Authorization: Compares authenticated user email with page Email property before tracking
  • Page Visibility API: Tracks when users switch tabs or minimize windows
  • Periodic updates: Calls viewing API every 4 seconds while page is active (authorized users only)
  • Session management: Automatic cleanup handles crashed browsers and network issues
  • Performance: Non-blocking API calls don't impact user experience, zero calls for unauthorized users
FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
ExploreDocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.