trip-scaffold
Compile a family-trip PWA. The output is a static artifact the user owns, not a
chat answer: cream-and-terracotta visual identity, a day-by-day schedule, a
contingency chip on every anchor (每個備案都有資料), a collapsible prep-refs card
(今晚先看), full keyboard a11y, an offline service worker, and static output ready
to publish to GitHub Pages.
This skill has three subcommands. Read the matching reference file in
references/ before executing — keep this entry short; the detail lives there.
When to use which subcommand
init — create a new trip PWA from scratch. Sets up the static shell
(index.html, day.html, css/, js/), the data corpus (trip.json, days.json,
refs.json), PWA icons (192/512 PNG + maskable, rendered from a city-initial
SVG via @resvg/resvg-js), and the service worker. Read
references/init.md.
draft-days — seed data/days.json with execution-level schedule blocks,
one researched contingency alternative per real anchor, retaining inline
research detail in the target language. Destination-local names use
local_name (jp_reading is a legacy Japan-only fallback).
Read references/draft-days.md.
launch-check — pre-publish audit: complete browser behavior (Playwright), static
duplicate-ref check, and the --quality family content-depth floor. Missing
bundle Playwright fails closed; --no-browser-tests is an explicit partial check. Exit
non-zero with an actionable message on failure. Read
references/launch-check.md.
If the request spans more than one subcommand (e.g. "make a Kyoto trip and draft
5 days"), run them in order: init → draft-days → (food-ingest / refs-ingest) →
launch-check.
Shared libraries
These live in skills/_lib/ (not user-invocable; imported by relative path):
scaffold.ts — the init engine. Run it directly:
bun skills/_lib/scaffold.ts --city <C> [--city-jp <漢字>] --days <N> --lang <lang> --start <YYYY-MM-DD> --out <dir>.
Builds everything in a sibling staging dir first. A fresh target is committed
with one atomic rename; an existing dotfile-only target uses rollback-protected
no-replace per-entry moves. If that existing-target commit fails, staging is
preserved, completed entries are rolled back when possible, and the command
prints both target and staging recovery paths. A failed fresh-target rename
leaves the target absent and removes staging.
Add --from-tokyo-seed with the still-required --out <dir>; the seed
supplies city, dates, day count, title, and default travelers for a
fully-populated Tokyo demo instead of an empty shell. Full flags + behaviour
in references/init.md.
draft-days.ts — the draft-days engine: bun skills/_lib/draft-days.ts --out <dir>.
launch-check.ts — the launch-check engine:
bun skills/_lib/launch-check.ts --out <dir> --quality family. It owns an
ephemeral loopback server and uses a bundle-owned runner/config/spec set;
the inspected trip contributes static files only.
router.ts — classifies a caption into a corpus. Used by food-ingest and
transpiled by init for the generated trip's edit-mode; refs-ingest does not
use it.
regenerate-sw.ts — scans data/*.json + static assets, computes a SHA-1
manifest, and fills %SW_VERSION% / %REQUIRED_SHELL_MANIFEST% /
%REQUIRED_CONTENT_MANIFEST% / %OPTIONAL_CONTENT_MANIFEST% in sw.js.
Every subcommand that writes
files calls this once at the end, so the offline cache stays in sync. After
an intentional direct JSON edit, recovery is
bun skills/_lib/regenerate-sw.ts --out <trip-dir>.
safe-trip-write.ts — rejects symlinked shipped trees, serializes cooperating
CLI writers with a trip-wide lock, and atomically replaces data/SW files. A
private helper verifies its cwd against the expected parent inode before
accepting bytes, then uses relative temp/write/rename operations; a later
parent-path ABA cannot redirect that commit into the replacement path, and a
final visibility check reports that the original directory moved. Do not
describe this as an absolute sandbox against every external filesystem mutation.
traveler-schema.ts — shared Traveler.age_band enum used by scaffold input
validation and the launch-check family profile.
icon-gen.ts — renders the city-initial SVG template to 192/512/maskable
PNG via @resvg/resvg-js. Called by init.
Hard rules
- Output is vanilla static. No build chain, no framework, no bundler. HTML +
CSS + JS + JSON that runs from
python3 -m http.server --bind 127.0.0.1 locally and deploys to gh-pages.
- Never overwrite app files.
init accepts a new/empty target or an existing
dotfile-only directory (preserving its dotfiles). If any non-dotfile entry
exists, it refuses and prints those entries. The user's existing trip is never
clobbered.
- Visual identity is fixed (approved variant A). Cream bg
#FFFCF7,
terracotta accent #E76F51, Hiragino Sans + Noto Sans TC, 17px / line-height
1.55. Token contract in templates/css/tokens.css. Do not invent a new
palette per trip.
- Contingency chips are always visible, never hidden behind a tap — the
curatorial depth (每個備案都有資料) is the differentiator; hiding it makes it
invisible.
- regenerate-sw runs after writes, batch-aware. When a subcommand writes N
files, call regenerate-sw once after all writes, not per file. A failure does
not roll back the data writes. Recover explicitly with
bun skills/_lib/regenerate-sw.ts --out <trip-dir> (or run another ingest
write); launch-check audits but does not mutate the trip.
- Empty states do the emotional work. A freshly-init'd PWA has empty corpus
on first open. Every empty surface carries a warm prompt + the next command,
never "No data". The first-open whole-app empty state is spec'd in
references/init.md — it is the highest-stakes screen.
Reference files
| Subcommand |
Reference |
When to load |
init |
references/init.md |
Before creating any trip PWA |
draft-days |
references/draft-days.md |
Before seeding days.json |
launch-check |
references/launch-check.md |
Before publishing |
Read only the reference for the subcommand you are running. Do not load all
three — that defeats progressive disclosure and wastes context.
1---2name: trip-scaffold3description: Compile a curated, offline-first family-travel PWA from a few prompts. Generates a vanilla static PWA (HTML+CSS+JS+JSON) with a schedule, per-anchor contingency plans, prep refs, an a11y baseline, and GitHub Pages-ready output — owned by the user, installable to the home screen, works offline. Use when scaffolding a new trip ("make a Kyoto family trip app", "build a trip PWA"), seeding a day plan (draft-days), or running pre-publish audits (launch-check). Pairs with food-ingest and refs-ingest for content.4---56# trip-scaffold78Compile a family-trip PWA. The output is a static artifact the user owns, not a9chat answer: cream-and-terracotta visual identity, a day-by-day schedule, a10contingency chip on every anchor (每個備案都有資料), a collapsible prep-refs card11(今晚先看), full keyboard a11y, an offline service worker, and static output ready12to publish to GitHub Pages.1314This skill has three subcommands. Read the matching reference file in15`references/` before executing — keep this entry short; the detail lives there.1617## When to use which subcommand1819- **`init`** — create a new trip PWA from scratch. Sets up the static shell20 (index.html, day.html, css/, js/), the data corpus (trip.json, days.json,21 refs.json), PWA icons (192/512 PNG + maskable, rendered from a city-initial22 SVG via `@resvg/resvg-js`), and the service worker. Read23 `references/init.md`.24- **`draft-days`** — seed `data/days.json` with execution-level schedule blocks,25 one researched contingency alternative per real anchor, retaining inline26 research detail in the target language. Destination-local names use27 `local_name` (`jp_reading` is a legacy Japan-only fallback).28 Read `references/draft-days.md`.29- **`launch-check`** — pre-publish audit: complete browser behavior (Playwright), static30 duplicate-ref check, and the `--quality family` content-depth floor. Missing31 bundle Playwright fails closed; `--no-browser-tests` is an explicit partial check. Exit32 non-zero with an actionable message on failure. Read33 `references/launch-check.md`.3435If the request spans more than one subcommand (e.g. "make a Kyoto trip and draft365 days"), run them in order: init → draft-days → (food-ingest / refs-ingest) →37launch-check.3839## Shared libraries4041These live in `skills/_lib/` (not user-invocable; imported by relative path):4243- `scaffold.ts` — the `init` engine. Run it directly:44 `bun skills/_lib/scaffold.ts --city <C> [--city-jp <漢字>] --days <N> --lang <lang> --start <YYYY-MM-DD> --out <dir>`.45 Builds everything in a sibling staging dir first. A fresh target is committed46 with one atomic rename; an existing dotfile-only target uses rollback-protected47 no-replace per-entry moves. If that existing-target commit fails, staging is48 preserved, completed entries are rolled back when possible, and the command49 prints both target and staging recovery paths. A failed fresh-target rename50 leaves the target absent and removes staging.51 Add `--from-tokyo-seed` with the still-required `--out <dir>`; the seed52 supplies city, dates, day count, title, and default travelers for a53 fully-populated Tokyo demo instead of an empty shell. Full flags + behaviour54 in `references/init.md`.55- `draft-days.ts` — the `draft-days` engine: `bun skills/_lib/draft-days.ts --out <dir>`.56- `launch-check.ts` — the `launch-check` engine:57 `bun skills/_lib/launch-check.ts --out <dir> --quality family`. It owns an58 ephemeral loopback server and uses a bundle-owned runner/config/spec set;59 the inspected trip contributes static files only.60- `router.ts` — classifies a caption into a corpus. Used by food-ingest and61 transpiled by `init` for the generated trip's edit-mode; refs-ingest does not62 use it.63- `regenerate-sw.ts` — scans `data/*.json` + static assets, computes a SHA-164 manifest, and fills `%SW_VERSION%` / `%REQUIRED_SHELL_MANIFEST%` /65 `%REQUIRED_CONTENT_MANIFEST%` / `%OPTIONAL_CONTENT_MANIFEST%` in `sw.js`.66 **Every subcommand that writes67 files calls this once at the end**, so the offline cache stays in sync. After68 an intentional direct JSON edit, recovery is69 `bun skills/_lib/regenerate-sw.ts --out <trip-dir>`.70- `safe-trip-write.ts` — rejects symlinked shipped trees, serializes cooperating71 CLI writers with a trip-wide lock, and atomically replaces data/SW files. A72 private helper verifies its cwd against the expected parent inode before73 accepting bytes, then uses relative temp/write/rename operations; a later74 parent-path ABA cannot redirect that commit into the replacement path, and a75 final visibility check reports that the original directory moved. Do not76 describe this as an absolute sandbox against every external filesystem mutation.77- `traveler-schema.ts` — shared `Traveler.age_band` enum used by scaffold input78 validation and the launch-check family profile.79- `icon-gen.ts` — renders the city-initial SVG template to 192/512/maskable80 PNG via `@resvg/resvg-js`. Called by `init`.8182## Hard rules8384- **Output is vanilla static.** No build chain, no framework, no bundler. HTML +85 CSS + JS + JSON that runs from `python3 -m http.server --bind 127.0.0.1` locally and deploys to gh-pages.86- **Never overwrite app files.** `init` accepts a new/empty target or an existing87 dotfile-only directory (preserving its dotfiles). If any non-dotfile entry88 exists, it refuses and prints those entries. The user's existing trip is never89 clobbered.90- **Visual identity is fixed (approved variant A).** Cream bg `#FFFCF7`,91 terracotta accent `#E76F51`, Hiragino Sans + Noto Sans TC, 17px / line-height92 1.55. Token contract in `templates/css/tokens.css`. Do not invent a new93 palette per trip.94- **Contingency chips are always visible**, never hidden behind a tap — the95 curatorial depth (每個備案都有資料) is the differentiator; hiding it makes it96 invisible.97- **regenerate-sw runs after writes, batch-aware.** When a subcommand writes N98 files, call regenerate-sw once after all writes, not per file. A failure does99 not roll back the data writes. Recover explicitly with100 `bun skills/_lib/regenerate-sw.ts --out <trip-dir>` (or run another ingest101 write); `launch-check` audits but does not mutate the trip.102- **Empty states do the emotional work.** A freshly-init'd PWA has empty corpus103 on first open. Every empty surface carries a warm prompt + the next command,104 never "No data". The first-open whole-app empty state is spec'd in105 `references/init.md` — it is the highest-stakes screen.106107## Reference files108109| Subcommand | Reference | When to load |110|---|---|---|111| `init` | `references/init.md` | Before creating any trip PWA |112| `draft-days` | `references/draft-days.md` | Before seeding days.json |113| `launch-check` | `references/launch-check.md` | Before publishing |114115Read only the reference for the subcommand you are running. Do not load all116three — that defeats progressive disclosure and wastes context.