# Playbook — novice user on a console client

Use this playbook when the user is a non-technical small-business owner and you are running in a client that can execute outbound HTTP requests like `curl`, read and write local files on the user's machine, and build a ZIP archive — but the person at the keyboard still doesn't want to hear about slugs, presigned URLs, or base64. Current examples of clients in this bucket: Claude Code, Cursor agents, Codex CLI, Gemini CLI, Claude Desktop with a shell MCP server attached (e.g. Desktop Commander), Aider, Continue. If your client cannot run `curl` or read local files, switch to `guide(topic="novice_web_chat")` instead.

This playbook is separate from the builder-console variant because the voice is different: the user here isn't a developer, so you still work in plain English and shield them from the moving parts, even though the primitives available to you are the full console set.

## Orient — a real website with its own address

Translate the platform into shopkeeper English before you speak. Where this bucket differs from the web-chat bucket: if the user has a photo folder, a logo SVG, or a prior site sitting on their disk, you can read it and push it directly — which changes nothing in how you talk about it, but saves the user a fetch-and-paste round trip.

> Say something like: _"I'll make you a proper website with its own web address — I can also pull in the photos on your laptop so we don't waste your time re-uploading anything."_

Instructions to yourself stay technical: the endpoints and MCP tools go by their real names. What you say aloud to the user stays in plain English.

## Auth — guest first, Google later

Default to `POST /auth/guest?token=true` and keep the access token in memory. Offer Google sign-in up front only if the user specifically says they want the work kept long-term from day one — otherwise, do the live-URL reveal first and offer the upgrade once they've seen their site. The "Converting guest to Google" path inside the shared reference you already loaded via `guide(topic="orient")` covers the `POST /auth/convert` flow.

> Say something like: _"I'll get your site on the screen first — you can sign in with Google in a minute if you want to keep it forever."_

## Project — pick the subdomain for them, never say the word

As in the web-chat novice variant, derive the lowercase-hyphenated subdomain yourself from the business name (_"Boiler Doctor Leeds"_ → `boiler-doctor-leeds`). Call `POST /projects` with it. On a rejection — duplicate, reserved, or malformed — quietly adjust and retry; the user never hears about the failed attempt. Only speak the accepted URL.

> Say something like: _"Your site will live at **boiler-doctor-leeds.uat-beam.page** — look alright?"_

Then open the reveal: show them the URL immediately.

> Say something like: _"Open **https://boiler-doctor-leeds.uat-beam.page** in a new tab — give it a few seconds to go live."_

Eventual consistency (the "Things to know" block of the shared reference you already loaded) means the first reload may show a placeholder; say so once and move on.

The default stance for this profile is **static pages plus the email action**. SPAs under `/app/` exist but are a builder-profile concern; don't introduce them here unless the user asks for something only an app can do (interactive filtering, a dashboard, live state) — then say once that _"that's a bigger piece of work — happy to build it if you want"_ and let them decide.

## First content — pick the upload method by shape, don't narrate it

On a console client you have four upload paths (the "Files" block of the shared reference you already loaded has the full picture). Pick by what the user already has, not by habit:

- **One small HTML file you wrote in chat** → inline `{"content": "..."}` via `PUT /projects/<id>/pages/_root/files/index.html` (or `upload_text({projectId, scope: "page", slug: "_root", filename: "index.html", content})`).
- **A logo, photo, or PDF already on the user's machine** → `POST .../files/<name>/upload-url` for a presigned PUT URL, then `curl -X PUT --data-binary @file "<uploadUrl>"`, echoing every header from the response exactly. Use `--data-binary`, not `-d` — `-d` strips newlines.
- **An image at a public web address the user already has** → `{"url": "https://..."}` URL-fetch in the same `PUT /files/<name>` call.
- **A whole folder of stuff (existing site export, photo folder, brand pack)** → ZIP it and push to the `upload-zip-url` endpoint.

Say what happened to the user, not how you got there. _"Your logo's on the site now"_ — not _"I PUT the bytes to S3 via a presigned URL."_

## Getting found on Google — fill in the metadata, don't ask

Before moving on to iterate, set up the search-engine basics without asking the user to write copy. Derive from what they already told you (business name, what they do, location if they gave one):

- A descriptive `<title>` (e.g. _"Boiler Doctor Leeds — emergency boiler repair across LS1–LS28"_).
- A one-line `<meta name="description">` written as a search-result snippet, 120–160 characters.
- OpenGraph tags for WhatsApp / Facebook / iMessage link previews: `og:title`, `og:description`, `og:image`, `og:url`. Point `og:image` at the default `/assets/logo.svg` seeded at project creation, or at a brand-coloured typographic card — never omit it.
- A `<script type="application/ld+json">` block with a schema.org `LocalBusiness` JSON-LD object. Pick the subtype that fits: restaurant / café / bakery → `Restaurant`; plumber / builder / electrician / locksmith → `HomeAndConstructionBusiness`; salon / barber → `BeautySalon`; accountant / solicitor / financial adviser → `FinancialService` or `ProfessionalService`; everything else → the base `LocalBusiness`. Include `name`, `address`, `telephone`, `openingHours`, and `geo` only if the user supplied them — a partial object is fine; a fabricated one is not.

Write the copy yourself. Do not ask the user _"what should the meta description be?"_ — they don't know.

Mention once that the site auto-publishes a sitemap at `/sitemap.xml` and they can submit that URL to Google Search Console if they want to speed up indexing. Don't walk them through Search Console itself. Don't create a `robots.txt` either: the platform already synthesises a permissive one on every subdomain.

## Images — a non-problem here

A photo the user hands you is trivial: ask for the path, request a presigned PUT URL, `curl` the bytes. File-system access is this profile's standout advantage over web chat — lean into it:

> Say something like: _"If you have photos or a logo saved somewhere on your computer — Downloads folder, Desktop, an old website backup — just tell me the folder and I'll pick them up directly."_

Offer that proactively at the "what should we show" step, before the user starts thinking they need to upload anything by hand. If they only have a public URL, URL-fetch it. If they have nothing, generate an SVG placeholder or pull from Unsplash. PDFs and video work the same way. The elaborate three-tier fallback needed on web chat isn't needed here — every tier of it is one call away, and public image-hosting services are not part of this profile's path.

## Email action — offer a form shaped to their business, not "a contact form"

The platform's `email` action takes any JSON payload from any form on the site and emails it straight to the owner. It isn't restricted to "contact us" — it's the general-purpose _something-happens-on-the-site → owner-gets-an-email_ mechanism. Use it for a baker's cake-reservation form, a plumber's quote request, a wedding planner's RSVP, an application form, a waitlist signup, a donation pledge.

The moment you know the business type — right after the live-URL reveal and the first content — offer the form by business shape, not generically:

> Say something like: _"Should customers be able to request a boiler-repair quote through the site, maybe with a photo of the problem? When they fill it in, the details come straight to your inbox."_

Same pattern for other business types — baker: _"should customers be able to reserve cakes or request special orders?"_; wedding planner: _"would you like an RSVP form for guests?"_. Don't ask _"do you want a contact form?"_

The action **requires a Google account**. That makes it the strongest lever for the Google conversion below — frame the conversion as _"if you sign in with Google, your customers can email you when they fill in the booking form"_, not abstractly as _"keep it forever."_

Offer once, then stop. For the HTML pattern, the Alpine.js scaffold, the rate limit (10 emails per project per hour), pre-filling via query params, and the exact body shape, consult `contact-form.md` — the file name narrows the framing: **the same pattern applies to bookings, RSVPs, quotes, applications, surveys, and waitlist signups**, not just contact-us forms.

## Iterate — conversational, but heavier edits are fine

Expect the same conversational change requests as on web chat (_"change the phone", "add Saturday hours"_) but here you can also regenerate local files and re-upload whole sections. For small textual changes prefer `POST /edit` (watch the typographic pitfalls inside the shared reference — curly vs straight quotes, `&mdash;` vs `—`, non-breaking space vs space); for anything that will change daily or weekly, switch to the scenario-specific guide so the next LLM in the conversation has the right pattern.

Depth targets for iteration on this profile:

- Daily-changing menu → `restaurant.md`
- Recurring or single event → `event-page.md`
- Listings, filters, search → `property-listings.md`, `job-board.md`, `map-with-pins.md`
- Photo gallery → `photo-gallery.md`

Reach these via `guide(topic="scenario_<name>")` (MCP) or by asking your client's HTTP tool to read the URL — don't re-derive their content.

## Durability — sell Google, mention snapshots once, stop there

After the reveal, offer Google conversion with the email-action framing above plus one bonus the console user can appreciate:

> Say something like: _"Once you're on Google, the platform keeps a backup of your site every night for 30 days — so if anything ever looks wrong I can roll it back."_

Mention the management UI once — `https://uat-beam.page/projects` — so they know they can see their projects in a browser without you. If they decline Google, don't nag; note the 2-hour guest-session window once and move on. Custom domains are coming but not available — match the "coming soon" phrasing from the shared reference.

## Do not

- Narrate `curl`, presigned-URL mechanics, S3, or "the bucket" to the user — say _"your logo's on the site now"_, not _"I PUT the bytes to S3 via a presigned URL."_
- Ask the user to pick a slug — derive it from the business name and only speak the accepted URL.
- Surface HTTP status codes or error bodies verbatim to the user — on a 409 / 400 / 403 from `POST /projects`, quietly retry with a new candidate; on anything else, translate before you speak.
- Recommend ImgBB, PostImages, or any public image-host for this profile — file-system access makes that path unnecessary; use presigned PUT from disk instead.
- Say _"do you want a contact form?"_ — propose the form shape that fits their business (booking / quote / RSVP / application).
- Re-call `guide(topic="orient")` when you need depth — the shared reference is already in your context; pick the relevant section by name.

## Next steps — hand off cleanly

When the user asks for something this guide didn't cover, don't improvise. Read the right source via `guide(topic="...")` or a direct HTTP GET:

- Running a React or Flutter SPA under `/app/` → `guide(topic="scenario_spa_react")`, `guide(topic="scenario_spa_flutter")` (https://uat-beam.page/llm/spa-react.md, https://uat-beam.page/llm/spa-flutter.md).
- Booking / quote / RSVP / application / contact form (requires Google) → `guide(topic="scenario_contact_form")` (https://uat-beam.page/llm/contact-form.md).
- Multi-page site, portfolio, multi-language — `guide(topic="scenario_multi_page_site")`, `guide(topic="scenario_portfolio")`, `guide(topic="scenario_multi_language")` — all listed in the scenario table inside the shared reference.
- Cloning an existing page as a starting point → `guide(topic="scenario_copy_page")` (https://uat-beam.page/llm/copy-page.md).
- General reference (errors, conventions, redirects, snapshots, edit semantics) → the `llm.txt` you already loaded via `guide(topic="orient")`.

The rest of the platform behaves identically regardless of whether you are on a console or a web-chat client — that's why the depth lives in the shared reference rather than being duplicated across playbooks.
