pl8ypus

Builder Log 003

Separating the public demo from the live dashboard.

Published / Architecture May 2026

A page that does two jobs does neither well. Audience Finder AI started with a single HTML file that was simultaneously a public marketing page, a portfolio demo, and a live Worker control surface. That worked for V1. By the time the live backend was real and the review loop was production-grade, the architecture needed to reflect that honestly.

This log covers the decision to split the page, what each page now owns, how the split was verified clean, and why the boundary matters beyond just tidying up the file list.

The problem with one page

The original audience-finder.html had a structural conflict. The top half was a public marketing and demo interface: simulated review queue, localStorage memory, a five-candidate session limit, and clear labelling as a demo. The bottom of the same JavaScript file was making live API calls to the Worker backend to display real status, pending queue counts, and Worker health.

The public demo needed to be safe to share, stable regardless of backend state, and honest about using simulated data. The live dashboard needed real Worker responses, write access to accept and reject candidates, and a clear signal that it was not a simulation. These requirements are in direct tension. Keeping them in the same file meant neither was fully clean.

A second problem was communication. A visitor following a portfolio link should see a demo that explains the workflow concept. Someone operating the live system should see live state and review controls. Showing both to both audiences serves neither.

The split

The solution was a clean file split: one HTML page per purpose, one JavaScript file per purpose, a verified boundary between them.

Public page

audience-finder.html

Marketing and demo interface. Explains the workflow concept, shows the architecture, and runs a simulated review queue backed by hardcoded seed data and localStorage. Zero API calls. Works offline. Safe to share.

Live dashboard

audience-finder-dashboard.html

Worker-backed control surface. Shows live status, the pending review queue, decision memory, recent discovery runs, and accept/reject controls that write to the live backend. No simulated data.

Public script

assets/js/audience-finder.js

Handles simulated discovery, localStorage decisions, domain memory, and local export. Fetches only against assets/data/audience-opportunities.sample.json. No Worker URL, no API base, no live routes.

Dashboard script

assets/js/audience-finder-dashboard.js

All Worker API calls live here. Status check, pending queue fetch, memory read, discovery run, accept/reject writes. No localStorage, no sample data, no simulation logic.

The public page: what it does and what it doesn't

The public demo page can now be described simply: it runs a simulated review queue in the browser using five hardcoded candidate seeds, stores decisions in localStorage, and exports reviewed records as JSON or CSV. That is the entire scope.

It does not call any external service. It does not display live Worker state. It does not write to KV. When a visitor accepts or rejects a candidate, that decision lives in their browser and nowhere else. This is the honest version of a public AI demo: it shows the workflow without pretending the local data is real discovery output.

  • Discovery source: simulated local seeds
  • Memory layer: localStorage only
  • Scoring: hardcoded fit scores on seed data
  • External calls: sample JSON file only
  • Live Worker status: not shown
  • Writes to backend: none

The live dashboard: the Worker interface

The dashboard page is the live control surface for the Audience Finder AI Worker at audience-finder-api.gs-c4a.workers.dev. On load it polls three endpoints in parallel: status, pending queue, and decision memory. The status strip shows whether the Worker, KV memory store, and AI scoring layer are available. The pending queue shows real candidates waiting for human review. The memory panel shows recent accept and reject decisions from the live KV log.

Accept and reject buttons call the live Worker write endpoints. A discovery run calls the controlled seed discovery route with a hard limit of five candidates per session. Recent runs show the discovery run history from the live backend. None of this is simulated.

One thing is still deferred: URL-level protection. The dashboard is not yet behind a login gate. The Worker's read endpoints are public. The write endpoints will accept calls from any browser that sends the right request. Proper authentication for the dashboard URL is the next architecture step.

  • Worker status: live check on load
  • Pending queue: real Worker candidates
  • Memory: KV accept/reject log
  • Discovery run: controlled seed pool, max 5
  • Review decisions: write to live backend
  • Auth protection: deferred

Verifying the boundary

The split is only meaningful if the boundary is actually clean. The verification pass confirmed both directions.

A grep for /api/audience, workers.dev, and API_BASE in the public script returned zero matches. The public script has no knowledge of the Worker URL and makes no live network calls beyond the local sample JSON file.

A grep for localStorage, DATA_URL, and runSimulationButton in the dashboard script returned zero matches. The dashboard has no simulation logic, no localStorage state management, and no reference to the sample data file.

Both files passed node --check syntax validation. The build ran clean and copied both pages to the output directory without errors.

The internal notice

A small amber banner was added near the top of the dashboard page to make the purpose explicit at a glance. It reads: Internal live dashboard. This page controls the live Audience Finder AI workflow. The public portfolio/demo page is audience-finder.html. A link to the public page is included.

This is not defensive design. It is communicative design. The notice exists so that anyone who opens the dashboard page -- whether by accident, during a demo, or in a handoff -- immediately understands what they are looking at and where the public-facing version lives. The amber colour borrows from the existing site convention: amber marks internal or deferred state, never live user-facing content.

What this proves

  • Separating demo from live is not just an architecture principle -- it is a communication decision. Each page now tells a clear story to its intended audience
  • A public demo that makes zero external calls is more honest about what it demonstrates than one that silently displays live data alongside simulated data
  • File-level boundaries enforced by verification pass are more reliable than conditional logic inside a shared file -- the grep result is the proof
  • An explicit internal notice makes a system easier to hand off, safer to demo, and clearer to reason about under time pressure
  • Deferring auth protection honestly (noting it in the HTML and the log) is better than shipping a half-implemented gate that gives false confidence

What comes next

  • Add URL-level protection for the dashboard page -- Cloudflare Access or client portal gate so the live control surface is not publicly reachable
  • Review local PowerShell tooling against the split: the tools/audience-admin/ scripts should have clear scope relative to the live Worker endpoints
  • Expand the controlled discovery seed pool as the live review loop proves reliable at current scale
  • Document the two-page architecture in the Audience Finder AI build page so the distinction is visible to portfolio visitors
  • Push the blog-003 entry to the live site and confirm the log index is current

See the system

The public demo is at the Audience Finder AI page. It runs entirely in the browser with no external calls. The live dashboard is at audience-finder-dashboard.html and controls the Worker-backed review loop. Previous build decisions are in Builder Log 002.

Split status

Public page: demo-only

Dashboard: Worker-backed

Public JS: API-free

Dashboard JS: Worker calls

Auth: deferred

Notice: banner added

Log entry

003 / Architecture

Product

Audience Finder AI

Phase

Public / live split