FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
glance
glanceGlimpse
Public
Like
Glimpse
Home
Code
8
.vscode
1
backend
4
frontend
5
shared
1
.cursorrules
INSTRUCTIONS.md
README.md
deno.json
Branches
3
Pull requests
Remixes
1
History
Environment variables
5
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
5/27/2025
Viewing readonly version of main branch: v1
View latest version
README.md

Glancer

Glancer addresses critical limitations in Notion's automation and logging capabilities by providing a robust external platform that integrates seamlessly with Notion databases. While Notion excels at data organization and basic automation, it lacks sophisticated error handling, detailed logging, and complex workflow orchestration. Glancer fills these gaps by moving the heavy lifting to Val Town, where we have full control over error handling, logging, and automation logic.

Understanding Notion's Limitations

Notion's built-in automation has several constraints that make external processing essential:

  • Limited Error Visibility: Generic error messages with no detailed logging or stack traces
  • No Advanced Error Handling: Cannot implement retry logic, complex error recovery, or custom error workflows
  • Performance Constraints: API rate limits and latency issues that affect user-facing experiences
  • Automation Complexity: Complex workflows are difficult to implement, debug, and maintain within Notion
  • No Real-time Processing: Limited ability to handle webhooks and real-time data processing

Glancer solves these by moving complex logic to Val Town where we have complete control over the execution environment, comprehensive error handling, detailed logging, and performance optimization through caching.

Key Technical Features

Task Routes Architecture

Task routes handle POST endpoints for Notion webhook processing, providing robust error handling and logging that Notion cannot offer:

// /routes/tasks/setAction.tsx import { setAction } from "../../controllers/setAction.tsx"; app.post("/", async (c) => { const { data } = await c.req.json(); try { const page = await setAction(data); return c.json(data); } catch (error: any) { console.log(error.body); return c.json({ status: "error", message: error.message, }, 500); } });

Task routes are essential because they provide:

  • Comprehensive error logging that Notion lacks
  • Custom error handling and recovery logic
  • Detailed request/response tracking
  • Webhook validation and processing

Separation of Routes from Controllers

Controllers contain business logic and Notion API interactions, while routes handle HTTP concerns. This separation keeps endpoints thin and business logic reusable:

// /controllers/setAction.tsx import { Client } from "npm:@notionhq/client"; const notion = new Client({ auth: Deno.env.get("NOTION_API_KEY"), }); export async function setAction(data: any) { try { const page = await notion.pages.create({ parent: { database_id: Deno.env.get("GLANCE_INTERACTIONS_DB_ID"), }, properties: { "Name": { title: [{ type: "text", text: { content: data.action } }], }, "Date": { date: { start: new Date() }, }, "Glancer demo": { relation: [{ id: data.id }], }, }, }); return page; } catch (error: any) { return { "message": error.message, "status": error.status, "import.meta.url": new URL(import.meta.url).pathname, }; } }

This separation provides:

  • Clear boundaries between HTTP handling and business logic
  • Reusable controllers across different routes
  • Easier testing and debugging
  • Better error isolation and handling

Advanced Logging

Every Notion interaction and automation workflow is logged with detailed context, providing visibility that Notion's built-in automation cannot offer:

// Comprehensive error logging in controllers catch (error: any) { return { "message": error.message, "status": error.status, "import.meta.url": new URL(import.meta.url).pathname, }; }

Logging is critical because:

  • Notion provides minimal error information
  • Val Town's logging is accessible and searchable
  • Detailed logs enable rapid debugging and issue resolution
  • Error context helps prevent recurring issues

Caching with Blob Storage

High-performance caching eliminates Notion API latency during customer-facing demonstrations:

// /crons/demoCache.tsx import { blob } from "https://esm.town/v/std/blob?v=12"; import { Client } from "npm:@notionhq/client"; export default async function(interval: Interval) { const pages = await notion.databases.query({ database_id: Deno.env.get("GLANCE_DEMOS_DB_ID"), }); for (const page of pages.results) { const blobKey = await blobKeyForDemoCache(import.meta.url, page.id); blob.setJSON(blobKey, { id: page.id, cobrowse: page.properties.Cobrowse.checkbox, }); } }

Caching is essential because:

  • Notion API calls can be slow during peak usage
  • Customer-facing demos must be responsive
  • Reduces API rate limit concerns
  • Provides consistent performance regardless of Notion's availability

Cron Jobs for Cache Maintenance

Scheduled tasks keep cached data synchronized with Notion databases:

// Cron job runs every minute to update cache export default async function(interval: Interval) { // Updates blob storage with latest Notion data // Ensures demos always have current information }

Cron jobs provide:

  • Automatic cache invalidation and refresh
  • Consistent data synchronization
  • Reduced manual maintenance
  • Reliable background processing

Environment Variables

Secure configuration management for Notion integration and API access:

// Environment variables used throughout the application const notion = new Client({ auth: Deno.env.get("NOTION_API_KEY"), }); // Key databases Deno.env.get("GLANCE_DEMOS_DB_ID") // Customer demo configurations Deno.env.get("GLANCE_INTERACTIONS_DB_ID") // Event tracking and analytics

Required environment variables:

  • NOTION_API_KEY: Notion integration token for database access
  • GLANCE_DEMOS_DB_ID: Database containing customer demo configurations
  • GLANCE_INTERACTIONS_DB_ID: Database for event tracking and analytics

React Frontend with Customer Personalization

Server-side rendered React components provide personalized experiences with durable customer references:

// /routes/views/demo/glancer.tsx app.get("/:id", async (c) => { const pageId = c.req.param("id"); const page = await notion.pages.retrieve({ page_id: pageId }); const props = page.properties; const agent = { name: props["Agent name"]?.formula?.string, email: props["Agent email"]?.formula?.string, phone: props["Agent phone"]?.formula?.string, }; return c.html( <html> <head> <script src="https://www.glancecdn.net/cobrowse/CobrowseJS.ashx?group=24456&site=staging"></script> </head> <body> <h1>A Glance cobrowse demo for {props.Name.title[0].text.content}</h1> {/* Personalized content based on Notion data */} </body> </html> ); });

The React frontend provides:

  • Unique URLs for each prospective customer
  • Personalized content and agent assignments
  • Durable references that persist across sessions
  • Customizable demo experiences

Demo Platform Features

The demo platform showcases Glance cobrowsing technology with several key capabilities:

Personalized Interface

Each prospective customer receives a unique demo page with:

  • Custom URL based on their Notion database record
  • Personalized agent contact information
  • Tailored demo content based on preferences
  • Persistent session state and tracking

Glance Cobrowsing Integration

JavaScript SDK integration enables real-time cobrowsing:

  • Shared screen viewing between visitor and agent
  • Remote assist capabilities for agent control
  • Real-time interaction with embedded content
  • Seamless integration with existing web applications

Glance PDF Viewer

Dynamic PDF generation and cobrowsing:

  • PDFs generated on-demand with customer-specific data
  • Shared PDF viewing and annotation
  • Real-time collaboration on document content
  • Integration with Notion data for dynamic content

Contextual Documentation

Developer-focused implementation guidance:

  • Collapsible help sections with implementation tips
  • Code examples and integration patterns
  • Best practices for cobrowsing implementation
  • Technical documentation for development teams

Simple Event Tracking

Comprehensive analytics for demo usage:

  • Click tracking for all interactive elements
  • Demo feature usage analytics
  • Customer engagement metrics
  • Real-time event logging to Notion database
// /utils/recordClick.tsx export async function recordClick(action: string) { const clickData = { id: pageId, "action": action, // cobrowse_start || cobrowse_request }; fetch("/api/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(clickData), }); }

Notion-Managed Components

Certain aspects of the application are handled by Notion rather than Val Town:

Email and Alerting

  • Email notifications for demo requests
  • Slack integration for agent notifications
  • Automated follow-up sequences
  • Alert management and escalation

Glancer Demos Database

  • Customer information and preferences
  • Agent assignments and contact details
  • Demo configuration and customization
  • Session scheduling and management

Interactions Database

  • Event tracking and analytics warehousing
  • Customer engagement visualization
  • Usage pattern analysis
  • Performance metrics and reporting

Agent Management

  • Cobrowsing agent assignments
  • Availability scheduling
  • Contact information management
  • Performance tracking and optimization

Architecture

Directory Structure

├── routes/
│   ├── api/          # GET endpoints for JSON data exposure
│   ├── tasks/        # POST endpoints for Notion webhook processing
│   └── views/        # Browser-rendered interfaces and demos
├── controllers/      # Business logic and Notion API interactions
├── utils/           # Stateless helper functions and frontend scripts
├── crons/           # Scheduled tasks for data caching and maintenance
└── index.tsx        # Main Hono application entry point

Key Components

Controllers vs Utils:

  • Controllers: High-level business logic, Notion API orchestration, stateful operations
  • Utils: Pure functions, string manipulation, date formatting, stateless helpers

Route Organization:

  • /api: GET endpoints returning JSON data for views and external services
  • /tasks: POST endpoints handling Notion webhooks and data processing
  • /views: HTML interfaces including the main cobrowsing demo platform

Getting Started

Environment Variables

NOTION_API_KEY=your_notion_api_key
GLANCE_DEMOS_DB_ID=your_demo_database_id
GLANCE_INTERACTIONS_DB_ID=your_interactions_database_id

Key Endpoints

  • / - Main application interface
  • /glancer/:id - Personalized demo pages for customers
  • /api/* - JSON data endpoints for frontend consumption
  • /tasks/* - Notion webhook handlers for automation
  • /demo/:id - React frontend with server-side data injection for demo pages

React Frontend with Data Injection

The /demo/:id route demonstrates an optimized pattern for React applications that need backend data:

// Backend route with data injection using existing API endpoint app.get("/demo/:id", async (c) => { const id = c.req.param("id"); try { let html = await readFile("/frontend/index.html", import.meta.url); // Use your existing API endpoint to get the data const apiUrl = new URL(c.req.url); apiUrl.pathname = `/api/demo/${id}`; const response = await fetch(apiUrl.toString()); const initialData = await response.json(); // Inject data to avoid extra round-trips const dataScript = `<script> window.__INITIAL_DATA__ = ${JSON.stringify(initialData)}; window.__DEMO_ID__ = ${JSON.stringify(id)}; </script>`; html = html.replace("</head>", `${dataScript}</head>`); return c.html(html); } catch (error) { // Fallback to serving HTML without initial data return serveFile("/frontend/index.html", import.meta.url); } });

The React component then uses this injected data with a fallback to API calls:

// React component with data consumption (types defined inline to avoid CORS) export function App() { const [demoData, setDemoData] = useState<DemoData | null>(null); useEffect(() => { // First, try to use initial data injected by the server if (window.__INITIAL_DATA__) { setDemoData(window.__INITIAL_DATA__); return; } // Fallback: fetch from API if no initial data const demoId = window.__DEMO_ID__ || getDemoIdFromPath(); if (demoId && demoId !== 'demo') { fetch(`/api/demo/${demoId}`) .then(response => response.json()) .then(setDemoData); } }, []); // Render with type-safe data handling return ( <div> {isNotionPage(demoData) && ( <div> <h3>Demo Page: {demoData.properties?.Name?.title?.[0]?.plain_text}</h3> <p>Setup Status: {demoData.properties?.["Setup status"]?.select?.name}</p> <p>Cobrowsing: {demoData.properties?.Cobrowse?.checkbox ? 'Enabled' : 'Disabled'}</p> </div> )} </div> ); }

This pattern provides:

  • Zero-latency initial render: Data is available immediately on page load
  • Graceful fallback: API calls work if server-side injection fails
  • Type safety: TypeScript types defined inline to avoid CORS issues
  • SEO optimization: Server-rendered content with dynamic data
  • Reuses existing API: Leverages your existing /api/demo/:id endpoint

Technical Stack

  • Framework: Hono (lightweight web framework)
  • Runtime: Deno on Val Town
  • Database: Notion API + Val Town blob storage for caching
  • Frontend: Server-side rendered JSX with Tailwind CSS
  • Scheduling: Val Town cron jobs for automated tasks
  • Authentication: Custom middleware for secure access

Development Philosophy

This project prioritizes:

  • Separation of Concerns: Clear boundaries between routing, business logic, and utilities
  • Error Transparency: Comprehensive logging and error handling that Notion cannot provide
  • Performance: Aggressive caching and optimization for customer-facing experiences
  • Maintainability: Well-documented code with clear architectural patterns
  • Notion Integration: Seamless bi-directional data flow with enhanced capabilities

By leveraging Val Town's capabilities, Glancer transforms Notion from a simple database into a powerful automation platform with enterprise-grade logging, error handling, performance optimization, and customer-facing demonstration capabilities.

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.