Imported from LarsArtmann/SKILLS (
website-launch/SKILL.md). Install upstream withnpx skills add LarsArtmann/SKILLS --skill website-launch. Copyright stays with the author.
Website Launch
Launches a public documentation website for a Go project (library or application/server) using the deterministic Astro + Starlight + Tailwind v4 + Firebase Hosting pattern shared across all LarsArtmann projects.
Path conventions: This skill assumes the LarsArtmann project layout:
~/projects/{repo}for project repos and~/projects/domainsfor the DNS Terraform repo. If your layout differs, substitute your own paths for{repo-path}and{domains-path}throughout. The{repo}placeholder in commands means the target repo name, not a shell variable — replace it before running.
This skill encodes lessons from 6+ prior sessions that each wasted 45-90 minutes rediscovering the same pattern, making the same mistakes, and writing the same feedback. Following this skill turns a 90-minute multi-agent exploration into a 25-minute scaffold-and-customize task.
Environment: Nix + Crush Constraints
All commands below assume a Nix-based environment and Crush CLI tool constraints. These apply throughout:
curlis BANNED in Crush. Use thefetchtool for HTTP requests, or Node.jshttps.requestfor scripted API calls. Everycurlin older references has been replaced.pnpm/nodeare not in PATH by default. Use:nix shell nixpkgs#nodejs -c pnpm install nix shell nixpkgs#nodejs -c pnpm run buildfirebaseCLI is not in PATH by default. Use:nix shell nixpkgs#nodejs nixpkgs#firebase-tools -c firebase --versionterraformis unfree (BSL license).nix shell nixpkgs#terraformfails. Use:
Or useNIXPKGS_ALLOW_UNFREE=1 nix shell --impure nixpkgs#terraform -c terraform planopentofu(the open-source fork):nix run nixpkgs#opentofu -- plangcloudis available via:nix shell nixpkgs#google-cloud-sdk -c gcloud auth print-access-token
Phase 0: Pre-flight Checks (BEFORE writing any files)
Run these checks first. If any fails, surface it to the user immediately before investing time in website creation.
First decision (§0.6): hosting target. The default is Firebase Hosting (custom domains, headers, the DNS pipeline in Phases 5–7). If the project needs none of that, GitHub Pages is a zero-infra alternative — decide early, it changes which phases apply.
0.0 Existing Website Check
Before starting any work, check if a website already exists:
ls ~/projects/{repo}/website/package.json 2>/dev/null \
&& echo "INFO: website/ directory already exists — check if this is an update or a rebuild"
If a website exists, switch to maintenance mode: read the existing files, identify what needs updating, and skip Phase 3 (creation). Do NOT overwrite existing customization without confirming with the user.
In maintenance mode, always audit for the "nice docs" patterns that predate this skill version — they retrofit onto any existing site without a rebuild:
- README: add "Who is this for?" and "When NOT to use this" if missing.
- Docs pages: add a curated "Where to go next" to each page; repeat the comparison table inside the relevant docs page.
astro.config.mjs: enablelastUpdated: trueandeditLink(see §3.10).- Swap bold-text warnings for
:::caution/:::tipcallouts. - Demo video: if the landing page has none, produce one (see §3.11 and the
demo video reference) — it retrofits onto any
existing site as a ShowcaseSection +
public/demo.mp4. If one exists, audit it against the current value prop (headline/message match, hook frame, README link, poster) — videos rot as the product evolves.
The full retrofit checklist is in the content patterns reference ("Retrofitting existing sites"). Running this audit on every existing site is how all implementations inherit these improvements.
0.0.1 Old Static Site Migration
If the project has an OLD static website (hand-written HTML/CSS/JS, not
Astro), it must be removed cleanly before creating the new website/
directory:
# Check for old static site
ls ~/projects/{repo}/site/index.html 2>/dev/null \
&& echo "INFO: old static site found in site/ — will need removal"
# Check for root-level firebase configs (should live in website/ only)
ls ~/projects/{repo}/firebase.json 2>/dev/null \
&& echo "INFO: root firebase.json found — must be moved to website/"
ls ~/projects/{repo}/.firebaserc 2>/dev/null \
&& echo "INFO: root .firebaserc found — must be moved to website/"
Migration steps (when ready):
trash(notrm) the oldsite/directorytrashroot-levelfirebase.jsonand.firebaserc(they now live inwebsite/)- Remove stale
.gitignoreentries referencing the old site (e.g.!site/*.html) - Check the old deploy workflow — it likely points to
site/as the public dir and must be replaced entirely
0.1 Firebase Project Decision: Shared vs Standalone
Most LarsArtmann websites deploy to the shared lars-software Firebase project
using hosting targets. Some projects (e.g. art-dupl) have their own
standalone Firebase project. Decide before writing any config files.
| Criterion | Shared (lars-software) |
Standalone |
|---|---|---|
| Default for new projects | Yes | Only if project needs its own infra |
.firebaserc |
"default": "lars-software" + targets |
"default": "{projectId}" |
firebase.json |
"target": "{name}" |
"public": "dist" (no target) |
| Deploy command | --only hosting:{target} --project lars-software |
--only hosting --project {projectId} |
| GitHub secret | FIREBASE_SERVICE_ACCOUNT (lars-software key) |
FIREBASE_SERVICE_ACCOUNT (standalone key) |
| DNS CNAME target | {siteId}.web.app. |
{projectId}.web.app. |
Check which pattern the project already uses:
# Check existing firebase config
cat ~/projects/{repo}/.firebaserc 2>/dev/null | grep -o '"default".*' \
&& echo "INFO: existing Firebase project found"
cat ~/projects/{repo}/firebase.json 2>/dev/null | grep -o '"target".*' \
&& echo "INFO: uses hosting targets (shared project pattern)"
# Or check for a website/-level config
cat ~/projects/{repo}/website/.firebaserc 2>/dev/null | grep -o '"default".*'
Throughout this skill, {firebaseProject} refers to whichever project you
chose. Replace lars-software in all commands with {firebaseProject} if
using a standalone project.
0.2 Credential and Infrastructure Check
# Namecheap DNS — can Terraform actually be applied?
cd ~/projects/domains 2>/dev/null || echo "WARN: domains repo not found"
# Check if API key is a placeholder
grep -q "REPLACE_WITH\|your_api_key\|xxx" terraform.tfvars 2>/dev/null \
&& echo "BLOCKED: Namecheap API key is a placeholder — DNS cannot be applied from this session" \
|| echo "OK: API key looks real"
# Firebase project exists?
nix shell nixpkgs#nodejs nixpkgs#firebase-tools -c \
firebase projects:list 2>/dev/null | grep -q {firebaseProject} \
&& echo "OK: {firebaseProject} project exists" \
|| echo "WARN: {firebaseProject} project not found"
# Check for existing hosting site collisions
nix shell nixpkgs#nodejs nixpkgs#firebase-tools -c \
firebase hosting:sites:list --project {firebaseProject} 2>/dev/null
0.3 Domain Naming
Decide the subdomain and site ID before creating anything. These are immutable after creation.
- Subdomain convention: Use the full repo slug when there is any risk of
collision with sibling projects (e.g.
go-workflow-auditlog, notauditlog— becausesamber-do-auditlogalso exists). - Short subdomains (e.g.
atomicwrite,gogenfilter) are acceptable only when no collision risk exists. - Check for collisions: Search the user's repos for similar names.
# Check existing DNS records for potential subdomain collisions
grep -r "{candidate-subdomain}" ~/projects/domains/lars.software.tf 2>/dev/null \
&& echo "WARN: DNS record already exists for this subdomain"
# Check user's GitHub repos for name collisions
gh repo list LarsArtmann --limit 100 --json name 2>/dev/null | grep -i "{keyword}"
0.4 Confirm Domain with User (BEFORE any commit)
Do not commit anything until the user confirms the subdomain name. A domain rename after commit creates stale git history, stale status reports, and requires touching 8+ files.
Present the proposed subdomain to the user and wait for confirmation:
"I'll use
{subdomain}.lars.softwareas the documentation URL, and{siteId}as the Firebase hosting site ID. The site ID is immutable. Confirm before I proceed?"
0.5 Name Collision Check
# Check existing DNS records for the subdomain
grep -r "{subdomain}" ~/projects/domains/lars.software.tf 2>/dev/null \
&& echo "WARN: DNS record already exists for this subdomain"
If pre-flight checks reveal blockers (placeholder credentials, IP not whitelisted), tell the user immediately:
"DNS records can be staged in Terraform but cannot be applied from this session — the Namecheap API key is a placeholder / the current IP is not whitelisted. I will prepare everything else and flag this as a manual step."
0.6 Hosting target: Firebase Hosting (default) vs GitHub Pages (alternative)
Most LarsArtmann sites use Firebase Hosting — it gives custom domains, security headers/CSP, and the DNS/Terraform pipeline in Phases 5–7. For a project that needs none of that, GitHub Pages is a zero-infra alternative (this is how the SP8D docs ship, for example).
| Criterion | Firebase Hosting (default) | GitHub Pages (alternative) |
|---|---|---|
| Custom domain + SSL | Yes (Phases 5–6) | Yes (repo Settings → Pages; slower cert) |
| Security headers / CSP | Yes (firebase.json, fix-csp.mjs) |
Limited (needs GH Actions to inject) |
| Cost / infra | GCP project | None |
| Skill phases used | All | Skip Phase 5 (DNS/Terraform) + Phase 7 SA |
Pick Firebase unless the project explicitly asks for GitHub Pages. If
GitHub Pages is chosen: the build is still astro build (output: 'static');
deploy dist/ via a actions/deploy-pages step; configure the custom domain
in the repo's Pages settings, not Terraform. The README rewrite (Phase 2),
website creation (Phase 3), and build verification (Phase 4) are identical.
Phase 1: Research the Project
Before writing any website files, understand the actual project:
- Read the project's
README.md,AGENTS.md,go.mod, andCHANGELOG.md. - Determine the project type (library vs application — see below).
- Read the source to verify all claims — see the Code Example Verification section below.
- Check for
GOEXPERIMENT=jsonv2requirements ingo.modorflake.nix. If the library usesencoding/json/v2, the README MUST document the build constraint. This is the #1 most commonly forgotten requirement.
Project Type: Library vs Application (CRITICAL decision branch)
This decision affects the hero code, docs pages, badges, links, config.ts fields, and README structure. Get it wrong and the website will have broken links to pkg.go.dev or misleading installation instructions.
How to tell: Check for a main.go or cmd/ directory:
# Application: has a main package with func main()
grep -rl "package main" cmd/ 2>/dev/null && echo "APPLICATION"
# Library: no main package, consumers import it
grep -rl "package main" cmd/ 2>/dev/null || echo "LIBRARY (probably)"
| Aspect | Go Library | Go Application / Server |
|---|---|---|
| Consumers run | go get + import |
Download binary / docker pull |
| pkg.go.dev | Yes — include link | No — omit entirely |
| Hero code | Go import + function call | Shell command or Docker run |
| Installation docs | go get, import path |
Binary, Docker, Nix |
| API reference docs | Go function/type signatures | HTTP endpoints, CLI flags |
config.ts |
Include pkgGoDev field |
Omit pkgGoDev field |
| Docs link bar | Documentation, pkg.go.dev, Changelog | Documentation, Changelog |
| README badges | Go Reference, Go Report Card | Docker, GitHub Release |
| GitHub topics | go, golang, library domain |
go, golang, server domain |
Library examples: go-atomic-write, gogenfilter, go-error-family Application examples: dynamic-markdown-site (binary + Docker + server)
Code Example Verification (critical)
Every Go code example in the website must be verified against the actual source. The #1 mistake across sessions is writing code examples from memory that don't compile.
Before writing any Go code into documentation:
# Verify function signatures
grep -A2 'func New\|func.*Add\|func.*Create' *.go internal/**/*.go
# Check return types (pointer vs value)
grep 'func.*\*.*{' *.go internal/**/*.go
Checklist for every Go code example:
- Does the function exist? (
grep 'func FunctionName' *.go) - Does the return type match the usage? (pointer vs value)
- Does the parameter type match the argument? (struct vs *struct)
- Does the method receiver match the call? (value vs pointer receiver)
Common trap: NewGraphNode returns *GraphNode but AddNode takes
GraphNode (value) — you must dereference: b.AddNode(*node).
Session-derived trap: Hero code showed WithDebounce(500*time.Second)
instead of 500*time.Millisecond. Always double-check time units in hero
and quick-start examples — this is the #1 most common code typo.
Application Content Verification
For Go applications/servers (not libraries), verify documentation claims against actual source:
# Verify CLI flags against actual flag definitions
grep -r 'flag\.\(String\|Bool\|Int\|Duration\)' internal/ cmd/
# Verify HTTP endpoints against actual route registrations
grep -r 'mux\.Handle\|HandleFunc\|http\.Handle' internal/
# Verify Docker details against the actual Dockerfile
cat Dockerfile
# Verify CI workflows exist
ls .github/workflows/
Common README errors for applications:
- Claiming a web framework (e.g. Gin) when code uses
net/http— verify viagrep 'gin-gonic\|gin-gonic' go.mod - Describing Docker builds wrong (e.g. "multi-stage" when single-stage) — read the actual Dockerfile
- Missing storage backends that exist in code (e.g. S3/GCS via gocloud.dev)
- Listing CLI flags that don't match
flag.StringVar/flag.BoolVarcalls - License mismatches between
.goreleaser.yamlandLICENSEfile
Phase 2: README Rewrite
The README is 50% of the public presence. It must match the quality bar set by go-atomic-write and gogenfilter. Do this BEFORE the website — the README defines the value proposition, comparison, and API tables that the website then visualizes.
Load the README template reference for the complete structure, badge templates, and section ordering.
Standard README Structure
The canonical section order (centered header → tagline → badges → docs
links → one-paragraph summary → ## Why? → audience → comparison → how it
works → when-NOT-to-use → install → usage → API tables → benchmarks →
license) lives in the
README template §"Standard Section
Order" — and only there. Earlier drafts of this SKILL.md restated the full
list; every restatement drifted from the template, so the template is the
single source of truth for structure, badge markup, and the documentation
link bar.
Why "Who is this for?" and "When NOT to use this"? They are the two highest-leverage trust signals in technical docs: the first tells a visitor whether to keep reading, the second proves the project tells the truth about its own limits. Both cost minutes to write. Load the content patterns reference for copy-paste templates for every section above, plus patterns for docs pages (curated "Where to go next", comparison tables repeated in docs, callouts, feedback links).
The value proposition is written once. The one-paragraph summary and "## Why?" section are the canonical sales narrative for the entire launch: the landing hero headline, the demo video's hook and value beat (§3.11), and any launch post copy are all derived from them — never re-invented per surface. When these drift apart, the page fights itself and each element undersells.
What to Remove
If the existing README has any of these, remove them:
- Emoji section headers (
## Features,## Zero Boilerplate) - Emoji bullet points
- Table of contents (unnecessary for GitHub rendering at <500 lines)
- "Made with heart" footer
- Redundant sections that duplicate the website docs
Badge row + Documentation Link Bar
Both live as copy-paste templates in the
README template ("Badge Template" and
"Documentation Link Bar" sections): the library badge set (with the
{LICENSE} placeholder — verify the real license, never assume MIT), the
application variant (Docker instead of Go Reference), and the link bar —
which gains a "Watch the {N}s demo" third link once the §3.11 video exists.
Do not re-invent either markup here; every restatement has historically
drifted from the template.
Phase 3: Create the Website
Load the file manifest to see exactly which files to copy verbatim, which to customize, and which to write fresh. The classification eliminates the need to read 60+ reference files via sub-agents.
Before designing, load the design inspiration reference to calibrate what "nice" looks like (the Nextra-class docs aesthetic, and how Starlight reaches it via theme tuning — not a framework switch).
3.1 Choose the Reference Baseline
Two reference repos exist. Use gogenfilter (~/projects/gogenfilter/website/)
as the baseline — it has CSP hardening, OG images, and a CI/CD pipeline.
go-atomic-write (~/projects/go-atomic-write/website/) is the older, simpler
pattern without these features.
| Feature | go-atomic-write | gogenfilter |
|---|---|---|
| Security headers | No | Yes (HSTS, X-Frame-Options, CORP, COOP) |
| CSP hash injection | No | Yes (fix-csp.mjs) |
| OG images | No | Yes (astro-og-canvas) |
| Two-job CI | No | Yes (build, then deploy) |
| Service account auth | No | Yes (GOOGLE_APPLICATION_CREDENTIALS) |
Security headers vs CSP: These are different things. Security headers
(HSTS, X-Frame-Options, Permissions-Policy, etc.) go in firebase.json
headers config. Content-Security-Policy (CSP) is a separate header that
restricts script/style/font sources. fix-csp.mjs post-processes built HTML
to inject SHA-256 hashes for inline scripts into the CSP header. Include
security headers in firebase.json always; add CSP hardening optionally
if the project needs it (copy from gogenfilter).
Important: Components are NOT verbatim copies between repos. They share
the same data-driven architecture (consume data from src/data/*.ts) but
differ in markup, styling, and sometimes structure. Copy from gogenfilter as
the starting point, then customize per-project.
3.2 Build the Website
-
Copy verbatim (~12 files) — see file manifest, "Copy verbatim" rows. These need zero changes.
-
Customize specific fields (~15 files) — change project name, URLs, accent color, sidebar structure. See file manifest, "Customize" rows.
-
Write fresh (~20 files) — project-specific content: features, hero code, sections, all docs pages, logo, favicon, and section components (CTASection, ComparisonSection, etc. — these follow the data-driven pattern from gogenfilter but need per-project markup and styling).
3.3 Accent Color
Load the color palette reference for
pre-computed CSS token sets for each supported accent color. Do NOT manually
compute rgba() values — use the table.
3.4 Dependencies
Load the dependency version reference for the verified package.json version matrix. Do NOT guess or bump versions — use the exact pins that are known to work together.
3.5-3.10 Website creation details
For favicon/logo design, MDX escaping, section component patterns, creation order, and Starlight config knobs, load ./references/website-creation-details.md.
3.11 Demo video — the launch's sales engine (default for every project)
Produce a 20-30s video that SELLS the project and embed it on the landing page. This is part of the launch, not a bonus: a working-product video above the fold converts where prose cannot, and it is cheap (~30-60 min) with HyperFrames (HTML/CSS/GSAP → deterministic MP4).
The video shares ONE sales narrative with the README and the landing hero (written once in Phase 2): its hook speaks the README "Why?" pain in outcome language, its value claim is the one-paragraph summary compressed to one sentence, and its final beat is the install command. A feature tour that never states the value is the known failure mode — it demonstrates but does not sell.
Load the demo video reference for the full
pipeline — script-before-pixels beat structure, routing through the
HyperFrames /product-launch-video workflow when the session has
hyperframes* skills (hand-rolled pipeline as fallback), NixOS invocation
(HYPERFRAMES_BROWSER_PATH, direct CLI path), seek-safe composition
rules, the canonical website/video/ location (commit the composition,
never stage it in /tmp), hero placement + README deep-link +
poster/og:image, mp4 cache headers, distribution tiers, and frame-level
verification without eyes.
Order of operations: ship the site first if the session is long, then add the video as an immediate follow-up commit — but do not skip it.
Phase 4: Build Verification
cd website
nix shell nixpkgs#nodejs -c pnpm install
nix shell nixpkgs#nodejs -c pnpm run build
If using pnpm v11+, native binary packages need approval:
nix shell nixpkgs#nodejs -c pnpm dlx approve-scripts esbuild sharp # if blocked
Expected output: N pages generated, sitemap, pagefind search index, 0 errors.
Run type checking and HTML validation:
nix shell nixpkgs#nodejs -c pnpm dlx astro check # 0 errors, 0 warnings
nix shell nixpkgs#nodejs -c pnpm dlx html-validate "dist/**/*.html"
Visual QA Gate (mandatory — Phase 4 does not start until this passes)
Across prior sessions, agents built 37+ files rendering to 15+ HTML pages and NEVER looked at any of them. Broken icons, CSS mismatches, and layout bugs were all latent. This gate exists because that is the single biggest quality risk in the entire workflow.
Step 1: Start preview server and verify HTTP responses
# Start preview server
nix shell nixpkgs#nodejs -c pnpm run preview &
sleep 3
# Verify key pages return 200 (use fetch tool, not curl)
# Check: http://localhost:4321/ — landing page
# Check: http://localhost:4321/getting-started/installation/ — first doc page
# Verify CSS variables resolved (use fetch tool to read the HTML)
# Look for 'color-accent' in the output — if missing, tokens are broken
Step 2: Headless screenshot (if available)
If Chromium is available via Nix, take a screenshot for visual verification:
nix shell nixpkgs#chromium -c chromium --headless --no-sandbox \
--screenshot=/tmp/website-landing.png \
--window-size=1440,900 http://localhost:4321/
Then view the screenshot with the view tool to verify:
- Hero section renders with code mockup
- Feature icons are visible (not broken SVG)
- Dark theme is applied by default
- No layout overflow or missing CSS
Step 3: Manual visual checklist
If neither headless screenshot nor browser access is available, flag the visual QA as incomplete and explicitly tell the user:
"I verified HTTP 200 responses and CSS token presence, but could not perform visual QA (no browser available). Please check the preview server at http://localhost:4321/ to verify: hero code mockup, feature icons, dark/light toggle, mobile layout, footer links."
Phase 5: Go-Live Sequence
Follow the exact sequence in ./references/go-live-runbook.md. Each step depends on the previous one. The runbook covers: lock files, Firebase site creation, deploy, web.app verification, custom domain REST API, ACME challenge extraction, DNS Terraform staging, Terraform apply, SSL provisioning wait, and custom domain verification.
Phase 6: GitHub Metadata
gh repo edit LarsArtmann/{repo} \
--description "{one-liner} — {key features}" \
--homepage "https://{subdomain}.lars.software" \
--add-topic go,golang,{domain-specific-topics}
Standard topic vocabulary
For Go libraries: go, golang, plus domain-specific topics (e.g.
error-handling, structured-errors, code-duplication, static-analysis).
For Go applications/servers: go, golang, plus deployment and domain
topics (e.g. web-server, markdown, docker, wiki).
Remove any topics that reference frameworks the project doesn't use (e.g.
remove gin if the project uses net/http).
Badge set (README)
Standard order for libraries: Go Reference | Go Report Card | CI | License.
Standard order for applications: CI | Docker | License (no Go Reference or Go Report Card — those are for importable packages only).
Social preview
Generate the card with the bundled
generate.sh (writes the SVG source +
the 1280x640 PNG into assets/branding/, computes font sizes to fit the
canvas, and machine-checks GitHub's limits):
website-launch/scripts/social-preview/generate.sh \
--title "linter-autoconfigure-sdk" \
--tagline "Shared foundation for linter auto-configuration tools" \
--install "github.com/larsartmann/linter-autoconfigure-sdk"
Then upload it (manual; no API) so repo cards render a designed card instead
of a README screenshot. Add --animate typing for a ~37 KB typing-loop GIF
(README embeds, Discord/Slack/Telegram). Load the
social preview reference for the verified
constraints (1280x640, < 1 MB), the no-deep-URL upload gotcha (Settings ->
General -> Social preview; no API), and the design rules + card-size
legibility workflow.
Documentation link bar
Do not re-invent a bar variant here — the README already carries the bar
Phase 2 placed. The canonical markup (HTML <p align="center"> form,
libraries vs applications) lives in the
README template "Documentation Link Bar"
section, including the "Watch the {N}s demo" third link that gets appended
once the §3.11 demo video exists. Verify the shipped README matches that
template — a bold-link bar here and an HTML bar in the template is exactly
the split brain that produced two different bars across launched repos.
Phase 7: CI/CD Setup (after manual launch is verified)
Only proceed after the custom domain is live and verified.
Step 1: Create Firebase service account key
# Find the firebase-admin SDK service account
nix shell nixpkgs#google-cloud-sdk -c \
gcloud iam service-accounts list --project={firebaseProject} | grep firebase-adminsdk
# Create a key (temp file — clean up after)
nix shell nixpkgs#google-cloud-sdk -c \
gcloud iam service-accounts keys create /tmp/firebase-ci-key.json \
--iam-account=firebase-adminsdk-{hash}@{firebaseProject}.iam.gserviceaccount.com \
--project={firebaseProject}
# Set GitHub secret
gh secret set FIREBASE_SERVICE_ACCOUNT --repo LarsArtmann/{repo} \
--body "$(cat /tmp/firebase-ci-key.json)"
# Verify
gh secret list --repo LarsArtmann/{repo}
# Clean up
rm /tmp/firebase-ci-key.json
Step 2: Add CI workflow
Load the CI workflow reference for the full template. It uses a two-job pattern:
- build-website —
pnpm install --frozen-lockfile,astro check,astro build, HTML validation, upload artifact - deploy-website — download artifact, deploy to Firebase via
GOOGLE_APPLICATION_CREDENTIALSsecret
The reference also includes rollback commands (firebase hosting:rollback)
for reverting a broken deploy.
Commit Discipline
Commit at these checkpoints — never leave a session with uncommitted work:
- After domain confirmed by user — Phase 0.4 gate passed
- After README rewrite — verified correct against source code
- After website builds successfully —
astro check= 0 errors,astro build= success - After Firebase deploy confirmed live — HTTP 200 on web.app URL
- After DNS records staged — Terraform
fmt+validatepass
Definition of Done
Before declaring complete, verify EVERY item on the checklist in ./references/definition-of-done.md.
Common Pitfalls
Load the common pitfalls reference for the full list. The most critical:
curlis BANNED in Crush — use thefetchtool or Node.jshttps.requestfor HTTP requests. Every command sample in this skill uses Nix invocations and Node.js, not curl.- Terraform is unfree — use
NIXPKGS_ALLOW_UNFREE=1 nix shell --impure nixpkgs#terraformoropentofu. pnpm,node,firebaseare not in PATH — always invoke vianix shell nixpkgs#{package} -c {command}.- Vite override conflicts — Remove
vitefrom overrides entirely. Astro 7 manages its own Vite version (Vite 8). Pinningvite: 7.3.2(copied from gogenfilter's older lockfile) causes a build failure withrollupOptions.input should not be an html file when building for SSR. Keep onlybrace-expansion,devalue, andyamlin overrides. See dependency-versions.md. --legacy-peer-depstrap — If you must use it, create.npmrcwithlegacy-peer-deps=true. Otherwise the lockfile is non-reproducible.- MDX character escaping —
<,>,<=,>=break.mdxfiles. package-lock.json+flake.lock— Must be generated and committed.GOEXPERIMENT=jsonv2— If the library usesencoding/json/v2, document the build constraint in README.- Stray dependencies — Never
pnpm installa package as a debugging step without removing it. Usepnpm dlxfor one-off commands. - Firebase upload endpoint —
upload-firebasehosting.googleapis.comis a different host. If unreachable, deploys fail. Verify before attempting. - bun vs Node.js for deploy —
firebase deployuses there2native module which fails under bun. Use real Node.js from Nix. - Edit tool collateral damage — When editing Terraform files, include
enough unique context and run
git diffafter every edit. - Committing before domain confirmation — The subdomain name touches 8+ files. Never commit until the user confirms the domain (Phase 0.4).
- Firebase custom domain API — Use
customDomainsendpoint (notdomains), body is{}with domain in query param,x-goog-user-projectheader is required. See firebase-rest-api.md. - Fabricated hero code — Hero terminal output or code snippets that don't match actual tool output. Always run the tool and capture real output for the hero section, or label it as illustrative.
- Auth secret mismatch — Upgrading from
FIREBASE_TOKENtoFIREBASE_SERVICE_ACCOUNTwithout adding the new secret first. Deploy fails on first push. Check which secret exists before changing the workflow. - Stale root-level docs — Old documentation files (HOW_TO_USE.md, USAGE.md, etc.) that overlap with the new website create confusion. Consolidate, archive, or link from README.
- Branch name in deploy workflow — The CI workflow template uses
master. If the project uses a different default branch (e.g.fork,main), update ALL branch references in the workflow YAML. - License mismatch — Verify the LICENSE file,
.goreleaser.yaml,flake.nix, andpackage.jsonall declare the SAME license. Prior session found.goreleaser.yamlclaiming MIT while the LICENSE file was proprietary — actively misleading Homebrew/Scoop/Nix repositories. - Lockfile package-manager mismatch — If the dev environment uses
bun installbut CI usespnpm install --frozen-lockfile, there's nopackage-lock.json. Either: (a) runpnpm installonce to generatepackage-lock.jsonfor CI, or (b) update CI to use bun and commitbun.lock(remove it from.gitignorefirst). - Application README claims wrong framework — A README that claims
Gin/Echo/Fiber when the code uses
net/httpdestroys credibility. Always verifygo.modfor the actual HTTP framework before writing the tech stack table. - Video composition left in
/tmp— HyperFrames work staged in/tmpis lost on reboot (happened on emeet-pixyd; only the committed MP4 survived). The composition lives in{repo}/website/video/from the first command. Also:firebase.jsoncache headers must covermp4|webm, or the video serves without long-cache headers.
