name: page-faq
description: Use when the operator wants a full client FAQ page assembled end-to-end from origin content. This is the Tier-3 composing skill - it invokes origin-scrape-faq, renders markdown via src/lib/faq-markdown.ts, writes the client's lib/faqs.ts, wires FaqPage.astro with shared faq-block.css + SectionBackdrop, and fires a preview build via build-trigger. Natural prompts include "build the FAQ page for revibetherapy.com", "let's do the FAQs for ", "stage 5c FAQ for ", "the origin FAQ changed - rebuild ours from it", "regenerate the FAQ page", or any phrase where the user wants a whole-page FAQ pipeline kicked off (not a single edit). ALSO covers service-page FAQs: "do the service pages have FAQs", "add FAQs + FAQPage schema to the service pages", "the service pages are missing FAQ schema" (see Service-page mode section). DO NOT use for: adding or editing one FAQ item (just edit lib/faqs.ts directly - much faster), styling tweaks on the FAQ accordion (edit src/styles/faq-block.css), changing accordion JS behavior (lives in revibe-therapy/layouts/RevibeLayout.astro for now, will move to shared layout next), or copy reviews of existing FAQ content.
allowed-tools: [Skill, Bash, Read, Write, Edit]
lib/faqs.ts directly - much faster), styling tweaks on the FAQ accordion (edit src/styles/faq-block.css), changing accordion JS behavior (lives in revibe-therapy/layouts/RevibeLayout.astro for now, will move to shared layout next), or copy reviews of existing FAQ content.
allowed-tools: [Skill, Bash, Read, Write, Edit]page-faq
Tier-3 composition skill. Owns the full path from "origin has FAQs"
to "preview site shows FAQs". Calls four other skills + the
renderFaqMarkdown helper.
When to use
- A new client migration is at stage 5c (About + Contact) and the FAQ page is one of the artifacts.
- Origin's
/faqs/content has changed and the client wants the site refreshed.
Required inputs
- Site identifier - slug (e.g.
revibetherapy.com) orsites.idUUID. - Origin FAQ URL - full URL, e.g.
https://revibetherapy.com/faqs/. If the user did not provide one, look up the site'sorigin_urlfrom Supabase and try<origin>/faqs/then<origin>/faq/then<origin>/frequently-asked-questions/until 200. - Client folder name in
moonraker-site-template/(e.g.revibe-therapy). If the user did not provide one, resolve via the site'sclient_folderfield or fall back to a sluggifiedsites.slug.
Execution
Run these steps in order. Halt on any failure and report.
- Scrape origin - invoke the
origin-scrape-faqskill with the origin FAQ URL. Capture output path (/tmp/<host>-faqs.json). - Render markdown - read the JSON. For each
{ q, a }pair, callrenderFaqMarkdown(a)(logic lives inmoonraker-site-template/src/lib/faq-markdown.ts). Output is either a string or{ html, block: true }ready for FAQAccordion. - Write per-client
lib/faqs.tswith this shape:
Use the existing per-client identifier convention if one exists (e.g. Revibe uses// Auto-generated from origin /faqs/ scrape on <ISO date>. export interface ClientFaq { question: string; answer: string | { html: string; block: true }; } export const clientFaqs: ClientFaq[] = [ ... ];revibeFaqs+RevibeFaq). Don't invent a new name if a precedent exists in the folder. - Wire FaqPage.astro in
<client>/templates/FaqPage.astroto:- Import
'../../src/styles/faq-block.css'at top of frontmatter. - Import
FAQAccordionfrom per-client components. - Import
SectionBackdropfrom../../src/components/SectionBackdrop.astro. - Wrap
<FAQAccordion faqs={clientFaqs} />in<SectionBackdrop>with the client's hero image. - Emit JSON-LD
FAQPageschema from the faqs (plain text only). - Bottom CtaSection optional but recommended.
- Import
- Astro build smoke - run
npx --no-install astro buildfrommoonraker-site-template/root. Halt on any error. - Commit + push - confirm with the operator before pushing. Use
a commit message like:
<client>: FAQ page from origin scrape (<n> Q+A) - Trigger build - invoke the
build-triggerskill with the site's slug,--target preview,--reason migration_complete. Suggest--waitso the operator knows when staging reflects the change. - Final URL - report
https://sites.moonraker.ai/<slug>/faq/for verification.
Output format
FAQ page built end-to-end
client: <slug>
origin: <FAQ URL>
faqs: <n>
lib: <client>/lib/faqs.ts
template: <client>/templates/FaqPage.astro
build: <build_id> staging: https://sites.moonraker.ai/<slug>/faq/
Service-page mode (FAQs on service pages, not the standalone FAQ page)
Proven 2026-06-11 on ITR (11 service pages) and Revibe (4 EN + 4 ES). Requirement: every service page carries a visible FAQ section AND exactly ONE FAQPage JSON-LD node, with every schema question also visible on-page (Google rich-results requirement).
Two situations, two paths:
- FAQ content does not exist yet (the ITR case): author 5-6 Q&As per
service grounded STRICTLY in that page's existing copy (overview,
science, phases, fit, benefits). No invented clinical claims, no
pricing numbers not already in the content, no outcome guarantees.
Store as a
faqfield on the per-client service content ({ eyebrow, title, items: [{ question, answer }] }, the aoth/itr shape), then render via the sharedbespoke-shared/components/ServiceFaq.astro, which emits the FAQPage JSON-LD itself. Do not build a new component. - FAQ content exists but schema is missing (the Revibe case: a UI-only accordion like FAQAccordion): leave the accordion untouched and build the FAQPage node in the service template frontmatter from the same faqs data, merged into the layout's schema array. Strip answers to plain text for JSON-LD (HTML tags, markdown links and emphasis), and guard against a second FAQPage node already present in the incoming schema array.
Origin scrape first: probe the live/origin service pages for existing FAQ content before authoring. Carry faithfully if found.
Local smoke for bespoke templates (no DB payload needed)
npx astro build from the repo root does NOT compile bespoke client
templates: they only enter the build when materialize writes wrapper
pages from the DB payload, which is VPS-only. To smoke a template change
locally, write temporary wrapper pages under src/pages/zz-smoke/ that
import the templates directly (mimic the materialized shape, e.g.
<ServicePage slug={'/emdr-therapy/'} />, or a [slug].astro with
getStaticPaths over all services), build, assert on dist/zz-smoke/
HTML (one FAQPage node per page, schema questions present in visible
markup), then DELETE the smoke dir before committing.
GOTCHA: do NOT name the smoke dir _smoke. Astro silently excludes
underscore-prefixed files/dirs under src/pages/ from routing: the
build "succeeds" while building none of your smoke pages. Use a
non-underscore name like zz-smoke.
Bugs to skip (encoded in shared lib + CSS; do not duplicate)
- The 8 bugs from
playbooks/site-migration-base.mdStage 5c apply here. Do not re-introduce per-page CSS for FAQ padding, specificity collisions, or list-marker alignment - they live insrc/styles/faq-block.cssonce. - Generator must use
renderFaqMarkdownfromsrc/lib/faq-markdown.ts. Do NOT regex^\d+\.\s+\*\*to detect lists; the helper handles blank-line-tolerant list grouping. - The materialize pipeline pulls
published_metafromsite_pages- if title/desc don't match what the template'sresolvedMetasays, update the DB row (do NOT change the template's spread order).
Boundaries
- Do not write FAQ content that wasn't on the origin. If a Q+A pair needs to be edited, surface that and let the operator confirm.
- Do not change
FAQAccordionstyling - only wire and pass data. - Do not push to a per-client repo -
moonraker-site-templateis the only repo. Per-client folders live as siblings undersrc/and<client>/.