Why every space needs a text twin, and the privacy rules those pages obey. · Rendered from docs/DISCOVERY-SEO-GEO.md in the project repository · view as Markdown

Discovery: search engines and AI engines as first-class audiences

Why this needs writing down

Everything the museum shows lives inside a WebGL canvas. To a crawler or an LLM, index.html is an empty page with a script tag: no room names, no artist statements, no artwork titles. A product whose entire content is invisible to search and to AI assistants cannot be found, cited, or recommended — and for a school project, "a parent asks an assistant about kids' art programs" is a real discovery path.

Three audiences, not one:

  1. People — the 3D experience.
  2. Search engines — need crawlable HTML, titles, descriptions, sitemaps, structured data.
  3. AI engines (assistants, answer engines) — need clean text they can read and quote, ideally without executing JavaScript.

Requirements

R1. Every space has a text twin

Each room, artwork, and exhibition gets a real URL serving server-rendered HTML: title, description, the artist statement, the medium, the class or program it belongs to, and a link into the 3D view at that spot. The canvas becomes the enhanced view of a page that already works as text.

R2. Structured data

schema.org JSON-LD on those pages: VisualArtwork for pieces, ExhibitionEvent / Collection for shows, Organization + EducationalOrganization for Lucas Academy, Person-free by policy (student identity stays out — use pseudonymous attribution).

R3. Machine-readable mirrors

R4. Social/answer previews

Per-page <title>, meta description, OpenGraph and Twitter cards with a real rendered image (a curated screenshot of the room, not a logo). These double as the thumbnail an answer engine shows.

R5. Language policy

EN + ZH only, site-wide, both indexable, hreflang pairs. This matches the knowledge platform's locked decision; do not add more locales here.

R6. Privacy is non-negotiable and comes first

Indexable text is a published surface: it may contain only what the public_snapshot layer allows (execution plan §2.3 / §5). No real names, no emails, no class membership, no draft history, no AI dialogue. When in doubt, a page is not indexed at all — noindex is the default for anything that is not a curated public snapshot. The current index.html carries <meta name="robots" content="noindex"> and keeps it until the public surface exists.

The worked example is /class/ (owner 2026-09-12). A lesson record names the children who were in the room, quotes what they said about being loved, and shows their faces. R6 does not forbid the page — it decides where the page lives: not indexed at all. The section is absent from sitemap.xml and llms.txt, sits behind Disallow: /class/, and every record and its index carry noindex, nofollow, noarchive, noimageindex; tests/class-section.test.js holds all four, because any one of them is easy to forget. What R6 cannot do is make an unlisted URL private — a link is a secret, not a door — so the rule for what belongs there is the same as for a printed handout: nothing that would harm a child if a stranger read it.

Reusable lesson plans are public teaching material (owner clarification, 2026-09-12). The six-lesson plan is published at /docs/van-gogh-love-lessons.html, included in the public docs navigation, sitemap and llms.txt. All lesson records and their index stay unlisted. Links may lead from a record to the public plan; public plans and their navigation must never reveal record URLs, student photographs or the private record index. Previously shared plan URLs use the public doc's navigation and canonical URL, so they do not retain a route into the records.

What this changes about hosting

Static Vite output cannot server-render the text twins. Two workable paths, to decide when the public surface is built:

What is built (2026-08-03)

npm run build runs scripts/prerender-discovery.mjs after Vite, which generates the readable surface from the SAME manifest and room documents the 3D world reads — so there is no second description to drift out of date:

The landing page itself is deliberately almost empty — the door and the controls, with everything else behind ?debug (owner 2026-08-03). Discovery therefore rides on the head metadata and the prerendered pages listed in the sitemap, not on visible text in the app shell. That is a real constraint: if a crawler ignores the sitemap it sees very little.

Still open