Skip to content

Latest commit

 

History

History

docs-website

Folders and files

README.md

Guaranate docs site

The user-facing documentation site for Guaranate, published at guaranate.dev: Astro Starlight, deployed with Zephyr Cloud.

This directory is self-contained — it holds the only package.json in the repository. The Swift package at the repository root does not know it exists, and swift build / swift test are unaffected by it.

Not to be confused with docs/ at the repository root, which holds agent and contributor configuration (docs/agents/, docs/adr/) and is not published here.

Commands

npm install        # once
npm run dev        # local dev server with hot reload
npm run build      # static build — and a Zephyr deployment (see below)
npm run preview    # serve the built site from dist/
npm run gen:cli    # regenerate the CLI reference from the guaranate binary
npm run gen:changelog # regenerate the changelog from the root CHANGELOG.md

The CLI reference is generated

src/content/docs/reference/cli.md is generated by scripts/gen-cli-reference.mjs from guaranate --experimental-dump-help, the Swift Argument Parser's own structured help output. Do not edit it by hand: every flag, short alias, and default on that page comes from the binary, so it cannot drift from what ships.

Regenerate it after any change to the CLI surface:

npm run gen:cli    # builds the binary if needed, then rewrites the page
npm run gen:cli:check   # non-mutating: fails if the page is stale

The page is committed because this site builds on Linux, where the macOS-only binary cannot be built. The Swift Build & Test CI job runs gen:cli:check on macOS, so a CLI change that forgets to regenerate the page fails there.

The changelog is generated

src/content/docs/reference/changelog.md is generated from the repository root CHANGELOG.md, the sole source for release history. npm run gen:changelog regenerates it, and npm run build and npm run dev run the generator first. If CHANGELOG.md changes while a dev server is already running, rerun npm run gen:changelog to refresh the page.

The page links “Edit page” to the root CHANGELOG.md. Generated output is ignored rather than committed: the docs build reads the source directly, so there is no second changelog to keep in sync. Unlike this page, the binary-generated cli.md is committed because the macOS CLI cannot be built on the Linux docs builder.

Brand assets

src/assets/brand/source-sheet.png is the original branding sheet: six mascot variants in a 3x2 grid on a black background. Everything else is cut from it by scripts/gen-brand-assets.sh (ImageMagick + pngquant), which paints out the few stray specks the sheet leaves outside a mascot's outline, flood-fills the black background to transparency, trims, and quantizes:

Asset Variant Used by
src/assets/brand/mascot.png plain mascot site header logo, README.md
src/assets/brand/terminal.png mascot in a terminal window landing hero, README.md
src/assets/brand/sticker.png die-cut sticker 404 page hero
public/brand/wink.png winking mascot install guide
public/brand/happy.png happy mascot sessions guide
public/brand/badge.png round badge source of public/favicon.png

Assets under src/assets/ are imported, so Astro optimizes and hashes them. Assets under public/ are referenced by absolute path from plain Markdown, where Astro's image pipeline does not apply.

./scripts/gen-brand-assets.sh    # only needed if the sheet or a crop changes

Variants used as a Starlight hero are padded to a square canvas: hero images are rendered at a fixed 400x400, and Astro crops to fill when both dimensions are set, which otherwise shaves the top and bottom off a non-square asset.

The social card

public/brand/social-card.jpg is the 1200x630 image link unfurlers show for every page — X/Twitter, Slack, Discord, LinkedIn. Starlight emits twitter:card: summary_large_image but no image of its own, so the og:image / twitter:image tags are declared in astro.config.mjs (head:), as absolute URLs built from site: unfurlers do not resolve relative paths.

Three details are load-bearing, all learned the hard way on X:

  • The card is a truecolor JPEG. It shipped first as a pngquant-palettized PNG, which X fetched (200, image/png, 1200x630, 73 KB) and then refused to render. Slack and Discord were fine with it.
  • The twitter: namespace is spelled out, not left to fall back to og:. src/components/Head.astro overrides Starlight's Head to add per-page twitter:title / twitter:description plus twitter:site / twitter:creator.
  • public/robots.txt allows everything explicitly. Without a file of its own the domain served Cloudflare's managed robots.txt, which is nothing but content-signal comments — no Allow, no sitemap.

X has no cache purge since the Card Validator was retired, and it caches the page metadata and resolved image as separate records. If X has fetched the image but omitted its image bindings, increment the version query on socialCard in astro.config.mjs to force a new media record; use a fresh page URL to make X read that updated tag instead of its cached page metadata.

./scripts/gen-social-card.sh    # only needed if the branding or the tagline changes

The card is composed by ImageMagick from src/assets/brand/terminal.png and the theme palette, then flattened (no alpha) and written at quality 88. Its type is set in Avenir Next and Menlo rather than the site's Space Grotesk / JetBrains Mono: Fontsource ships those as WOFF2 only, which FreeType — and so ImageMagick — cannot read.

Theme

src/styles/theme.css is the whole visual identity, wired through Starlight's customCss:

  • Colour. Starlight builds its palette from HSL ramps at hue 224 (cool grey). The same ramps are rotated to a warm hue and the accent ramp is rebuilt around the mascot's berry red (#d31a0e), which preserves Starlight's contrast steps. Aside and card accents are remapped to leaf green, guaraná amber, and berry red instead of Starlight's blue/purple rotation.
  • Type. Space Grotesk for UI and prose, JetBrains Mono for code, both self-hosted via Fontsource — no requests to a font CDN. Swapping the UI face means changing one Fontsource package in astro.config.mjs and --sl-font in src/styles/theme.css.
  • Details. Tighter heading tracking, a leaf-green rule under h2 section headings, hero art at 80% of Starlight's default size with a berry glow behind it, and a berry-red primary call to action.

Deployment

Zephyr deploys during npm run build — there is no separate upload step, and a git repository must be initialized (CI checkouts satisfy this). Consequences:

  • output must stay 'static'; Zephyr supports Astro's SSG mode only.
  • Authentication is a Zephyr token in ZE_SECRET_TOKEN, stored as the repository secret of the same name.
  • ZE_FAIL_BUILD: true is set in CI. Without it, Zephyr logs deployment errors and lets the build pass.
  • SKIP_ZEPHYR=true drops the integration from the Astro config, producing a plain static build. CI sets it whenever no ZE_SECRET_TOKEN is available, because with no credentials the plugin blocks on an interactive auth flow until it times out — five minutes for a build that takes two seconds.

.github/workflows/docs.yml deploys every merge to main — which Zephyr serves from the production domain, https://guaranate.dev — and internal pull requests, which get their own immutable preview URL. It build-verifies pull requests from forks, which cannot read repository secrets, and so intentionally run without a token and without deploying.

Deployment is intentionally detached from the binary's release: publishing the site never waits for a version to be cut, and cutting a release never republishes the site. Whatever is on main is what guaranate.dev serves.

Building locally deploys if you are logged in to Zephyr. astro preview loads the integration too, so with an expired session it stops and waits for a browser login. Either way, SKIP_ZEPHYR=true gives you a plain local site:

SKIP_ZEPHYR=true npm run build
SKIP_ZEPHYR=true npm run preview