The promo site, help docs, and feature catalog
One pipeline turns the feature catalog into every public-facing surface:
flowchart LR
feature["features/<id>.yaml"]
tour["tools/runstatus/src/tour/generated/<id>.ts<br/>(committed)"]
overlay["live tour overlay<br/>(web UI)"]
spec["Playwright *-video.spec.ts"]
artifacts[".artifacts/<dir>/<br/>demo MP4 + step PNGs + chapters.json"]
stitch["stitch-tour.mjs<br/>(product-tour sections)"]
master["complete-product-tour.mp4<br/>+ merged rail"]
index["features-index.json<br/>+ qa/<id> scenarios and feature docs"]
site["tools/site<br/>(VitePress)"]
pages["GitHub Pages<br/>base /Kitsoki/, full videos"]
help["internal/helpdocs go:embed<br/>kitsoki web /help/, posters only"]
feature -->|"codegen"| tour
tour --> overlay
tour --> spec
spec --> artifacts
artifacts --> stitch --> master
feature -->|"codegen"| index
feature --> site
index --> site
artifacts --> site
master --> site
site --> pages
site --> help
The feature catalog (features/)
One YAML per feature: title/tagline/summary (promo + docs copy), the tour steps (drive both the live overlay and the recorded demo), the demo's recording binding, optional gated ui-qa scenarios, doc links. Authoring guide: features/CLAUDE.md. The committed manifests under tools/runstatus/src/tour/generated/ are code-generated — make features regenerates, make features-check (inside make build and make test) fails on stale output, schema violations, or spec↔feature drift. Chained into it, pnpm demos:lint (scripts/features/lint-demos.ts) fails any demo spec that bypasses the camera helper, omits its chapter sidecar, or drives a live model — the no-LLM invariant, in CI. The chain YAML → generated TS → live popover is closed end-to-end by the existing video specs' title assertions.
Demo recording (make demos)
scripts/record-demos.sh records every recordable demo at watch-speed (WEB_CHAT_PACE=1), deterministically (no LLM — --flow/--host-cassette). Incremental by per-demo content stamps (feature YAML + spec + story inputs + binary) in .artifacts/<dir>/.stamp; make demos-force ignores them. One demo: make demo-feature FEATURE=<id>. Videos are never committed. Each spec also emits a <video>.mp4.chapters.json sidecar (one chapter per tour step) the site uses for its clickable chapter rail.
Every recording context comes from one device-profile registry (tests/playwright/_helpers/camera.ts): cameraContext() sources the viewport, scale, and recordVideo.size, so every section shares the 1600×900 canvas the stitch composes on (drift there silently letterboxes the master). demo.profiles (default [desktop]) is the device-matrix dimension — desktop is the only enabled profile until a demo's UI is responsive; mobile/tablet are a per-demo opt-in, recorded under KITSOKI_DEMO_PROFILE with a --<profile> filename suffix (desktop stays <base>.mp4, so its whole pipeline is unchanged). Ports come from demoAddr(basePort): basePort + KITSOKI_DEMO_PORT_BASE (a concurrent session/worktree sets the env to claim a free range) plus the profile's offset — so concurrent sessions and parallel profile passes never collide on a port. (The matrix is threaded through record + index today; the site-side variant serving — per-profile stage-media outputs + ChapteredVideo runtime switching on viewport — lands when the first demo actually goes responsive, so nothing ships a shrunken-desktop "mobile" cut in the meantime.)
The master product tour (make render-tour)
features/complete-product-tour.yaml (kind: product-tour) is stitched, not recorded: it declares ordered sections, each with clips referencing a demo-bound feature (optionally trimmed to a [startChapterId, endChapterId] window of that feature's sidecar). Because its video is composed, the master's demo has no spec (demo.spec is optional only for a sectioned product-tour; record-demos skips it). scripts/features/stitch-tour.mjs resolves each clip's per-profile MP4 + sidecar, ffmpeg-trims to the window, renders a title card per section, composes via the shared concat-videos.sh, and merges the per-section sidecars into one master rail — section-prefixed ids, a group/group_label per section, cumulative offsets (each card included, probed for exact duration), and a preserved source_ref for deep-linking. ChapteredVideo.vue renders that into one collapsible block per section (the 8-group rail); a plain per-feature sidecar still renders flat. make render-tour records any stale sources then stitches; the stitch is incremental (skips when the master is newer than every input — KITSOKI_STITCH_FORCE=1 rebuilds) and pure no-LLM post-processing.
The site (tools/site, VitePress)
- Promo landing (
src/index.md) is a thin layer: hero +<HeroDemo/>+<FeatureGrid/>over the same data/components as the docs pages — zero duplicated content. - Feature pages are dynamic routes (
src/features/[id].md+[id].paths.ts) over the generatedfeatures-index.json: chaptered video, step cards (click → seek), narrative markdown, doc links. - Slidey deck pages are dynamic routes (
src/decks/[id].md+[id].paths.ts) over top-leveldocs/decks/*.jsonfiles:/decks/renders title-slide gallery cards, and each deck page keeps the VitePress site chrome while embedding the committed self-contained viewer fromdocs/decks/bundled/<deck-id>.html. - Runtime workspace is
.temp/site.tools/siteis source only;make site/make site-devprepare.temp/site, stage generated guide docs and media there, and run VitePress from that writable root so Vite loader scratch files never land beside source files. - Guide docs are an allowlist copy of
docs/(docs-manifest.json+scripts/stage-docs.mjs): internal trees (proposals, competitive-analysis, skills, …) can never leak; links escaping the allowlist are rewritten to GitHub URLs; dead links fail the build;scripts/check-leaks.mjsre-checks the dist. Top-leveldocs/decks/*.json/*.slidey.jsonlinks are the exception: they rewrite to the local/decks/<deck-id>.htmlproduct-site page somake site-devpreviews newly added decks before they exist on GitHub. - Localization publishes English at
/, Thai at/th/, and Japanese at/ja/. Static locale pages live undertools/site/src/<locale>/; generated feature pages read optional JSON overlays fromtools/site/i18n/<locale>/. Missing fields fall back to English, so translation can move feature by feature. Usestories/product-site-localization/to draft or refresh those overlays, then accept only after the deterministic site build passes. - Missing media never fails a build — pages degrade to poster + placeholder, so docs-only iteration works with an empty
.artifacts/. - The media organization contract is documented in
docs/media/README.mdand checked bymake media-check(also part ofmake testwhen Node/pnpm dependencies are installed). It verifies feature demo paths, stagedpublic/media/<feature>/shape, and Slidey deck/gallery embeds without recording videos or invoking an LLM.
Targets: make site (build, base /Kitsoki/, output .temp/site/dist), make site-dev (prepare and serve .temp/site; rerun after source changes), make site-full (demos + site), make site-clean.
Publishing
- GitHub Pages:
.github/workflows/site.ymlbuilds the binary, records stale demos (two-level cache:actions/cacheover.artifacts+ the per-demo stamps), stitches the master product tour, builds, deploys. Docs-only pushes deploy in minutes with 0 recordings; a cold run records every recordable feature in the catalog (30–50 min). Recording (Record demos) and stitching (Stitch master product tour) arecontinue-on-error— cached media from a prior run can ship if either fails — but a subsequent hard gate (make media-check-promo, nocontinue-on-error) fails the build if any promo-grid feature ends up with no staged media at all, so silent all-stale ships can't happen. Any recording failures are also written to the job's step summary.scripts/check-download-links.mjsHEAD-checks download.md'sreleases/latest/download/...links (temporarily non-blocking until the v0.1.0 release assets publish — see the TODO in site.yml). Manual dispatch has arerecordinput. One-time setup: repo Settings → Pages → Source: GitHub Actions. - In the binary:
make site-embedbuilds the embedded variant (base/help/, posters only — never MP4s, ~5MB) intointernal/helpdocs/assets/; the nextmake buildembeds it andkitsoki webserves it at/help/. Unstaged help yields an actionable placeholder page, never an error (internal/helpdocs).
QA (gated — real LLM, never automatic)
make feature-qa FEATURE=<id> records the demo then judges it against the catalog-generated scenarios + feature spec (.artifacts/features/qa/<id>.{scenarios.yaml,feature.md}) via the kitsoki-ui-qa pipeline. make demo-tour-qa is the onboarding-tour instance of the same flow. For the stitched master (which has no recording spec), make tour-qa renders it via render-tour then judges the master video against its generated scenarios the same way.