Getting started (operator)
This guide takes you from a fresh clone to a shared white-label report. No external accounts are needed — the default database is a local file, and the offline mock engine lets you exercise the whole workflow.
1. Local quickstart
Requirements: Node ≥ 20.9 (Node 22 LTS recommended) and pnpm 10.
git clone <your-repo-url> placeable && cd placeable
pnpm install
cp .env.example .env.local
Open .env.local and set:
SESSION_SECRET— at least 32 characters of real entropy:openssl rand -base64 32OPERATOR_EMAILandOPERATOR_PASSWORD— your own operator account.pnpm seedcreates it (FR-1.6). This is also the account you recover withpnpm user:set-passwordlater.
Everything else can stay at its default (DB_DRIVER=pglite, database file at ./.data/pg). Then:
pnpm db:setup # apply committed migrations
pnpm seed # create the operator (and demo fixtures when DEMO_MODE=1)
pnpm dev # http://localhost:3000
Sign in at /sign-in with your OPERATOR_* credentials.
Demo credentials
When the app runs with DEMO_MODE=1 (or you seed with pnpm seed -- --demo-fixtures), a fully populated demo world exists with two documented accounts. These are defined as constants in src/db/demo-fixtures.ts — public demo access is a stated, accepted decision (FR-8.1):
| Role | Password | |
|---|---|---|
| Operator | demo-operator@placeable.dev | operator-demo-2026 |
| Agency user (Northlight Digital) | demo@northlight.example | agency-demo-2026 |
2. Your first workflow
Create an agency
Agencies are your tenants — the marketing agencies who resell reports. Go to Agencies → New agency: name, contact email, and an initial user. The initial user's password is shown once with a copy button; store it before dismissing.
Create a brand
A brand is an agency's end client — the business being measured. On the brand form, three fields do the real work:
- Aliases — every other name the business goes by ("Acme", "Acme CRM", "AcmeHQ"). Mention counting is name matching; a missed alias is a missed mention.
- Domains — every domain the business owns. Anything cited on the brand's own domain is scored LOCKED (you can't "get onto" your own site; it doesn't count as third-party evidence).
- Competitors — each with their own aliases and domains. Competitor mentions build the gap analysis, and competitor-owned domains are scored LOCKED too (a competitor won't list you on their site).
Create a prompt set
A prompt set is the list of buying questions you'll ask the engines, plus which engines and how many repeats.
What makes a good buying question: write what a real buyer types into an assistant, not a keyword. "I'm a loan officer and need a better way to manage my pipeline of leads. What's the best CRM?" beats "best mortgage CRM" — long-tail, first-person, with context. Ten to thirty prompts per set is typical.
Templates: "New from template" copies a code-shipped set (src/lib/templates.ts) into the brand, fully editable afterwards. Shipped templates: mortgage-saas, dental-software, hvac-software — each ten real buying questions at 8 runs/prompt across chatgpt, gemini, and sonar. Two more real engines exist beyond what the templates default to — Google's AI Overviews and AI Mode (ai_overviews, ai_mode) — and can be added to any prompt set; see Engine internals for what each one actually measures and the model each does or doesn't disclose.
Why 8 runs minimum: the same prompt to the same engine gives different answers on different runs — even at temperature 0, answers vary 9–28%. One run is an anecdote. Schulte et al. put the minimum at seven runs per prompt per engine for brand-level visibility and at least eight when source-level coverage matters; Placeable measures sources, so eight is the floor. The default is 8; anything below it requires a confirm dialog and permanently marks the resulting runs and reports with a low-sample warning chip. The hard floor is 3.
Start a run
From the brand page, Start run opens the preflight: total samples ("30 prompts × 8 runs × 3 engines = 720 samples"), estimated cost and duration, and per-engine configured status. An unconfigured engine blocks the start — there is no silent fallback to fake data.
- Real providers (
chatgpt,geminivia DataForSEO;sonarvia Perplexity;ai_overviewsandai_modevia DataForSEO's Google SERP endpoints): set the provider env vars (see.env.example), then run the local worker in a second terminal:pnpm worker. It claims queued runs and executes them in batches; Ctrl-C is safe, it resumes where it left off. - Mock engine: available in dev without any keys — deterministic offline fixtures, good for exercising the pipeline. In local dev, mock runs also go through
pnpm worker. In demo mode (DEMO_MODE=1) every run is mock-only, capped at 60 samples, and executes synchronously inside the request — no worker needed.
Watch progress on the run page: per-engine counts, cancel, and "Retry failed" for errored samples.
Generate the report and share it
When the run completes, Generate report creates an immutable snapshot (titled "Brand — Report #1", editable). On the report page, create a share link — a long unguessable URL your agency sends to their client. Links are revocable, can expire, and render the white-label view with the agency's logo and accent. In demo mode, share links may stop working at any time (data is in-memory).
Every report page also offers a print view (/reports/[id]/print, and /r/[token]/print on a share link) that renders the identical report component in a paper frame — "PDF" just opens the browser's own print dialog, nothing is generated or stored server-side — plus sources.csv and mentions.csv downloads; a full samples.csv of every sampled answer is available from the app (not the share link). A run started as a pitch snapshot rather than a full period measurement — one day, one wave, at the same 8-runs-per-prompt-per-engine floor — is labelled "Snapshot" everywhere its rate appears and is excluded from deltas and the monthly trend.
3. Next steps
- Explaining the report to a client: Agency guide
- What the numbers mean and why: Methodology (technical)
- Deploying for real: Supabase runbook