From 3fab83beffbf8cb50e6127fb4595b2f069967933 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Sun, 23 Aug 2026 21:21:38 -0700 Subject: [PATCH 1/6] docs: improve how the repository presents itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three gaps that all cost the project the same thing — someone finds Onebox and leaves without understanding what it is or how to engage with it. Issue and pull request templates. There were none, so a bug report arrived without a version, a target host, or the plan output that shows the discrepancy, and a proposal arrived without saying whether it widens the one-application, one-host scope. The templates ask for exactly what a maintainer needs on the first round trip, and the config points security reports at SECURITY.md rather than a public issue. A social preview card. GitHub and every chat client showed a generic grey box for links to this repository. Worse, Starlight emits `twitter:card: summary_large_image` and never an image to pair with it, so links to onebox.run unfurled as an empty large card. The card is rendered from `docs/media/social-card.typ`, and the site serves it as its og:image. A comparison table. The README never named Docker Compose, Kamal, Dokku, Coolify, Ansible, or Kubernetes, which are the words someone uses when they are looking for a tool in this space. Each row states the boundary difference rather than a feature score, and the Kubernetes row says plainly what Onebox gives up. Ran `just check`: passes. Did not run `just e2e`; nothing here touches the binary or the deployment path. Co-Authored-By: Claude Opus 5 (1M context) --- .github/ISSUE_TEMPLATE/bug_report.yml | 80 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 +++ .github/ISSUE_TEMPLATE/feature_request.yml | 53 ++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 30 ++++++++ README.md | 16 +++++ docs/media/social-card-mark.svg | 4 ++ docs/media/social-card.typ | 47 ++++++++++++ site/public/social-card.png | Bin 0 -> 55829 bytes site/src/components/Head.astro | 19 +++++ 9 files changed, 260 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 docs/media/social-card-mark.svg create mode 100644 docs/media/social-card.typ create mode 100644 site/public/social-card.png diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..8c9059e6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,80 @@ +name: Bug report +description: Onebox did something other than what it said it would do. +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Onebox holds a production application on someone else's server, so a + report that lets a maintainer reproduce the failure is worth far more + than a description of it. Please include the plan or command output + that shows the discrepancy. + + If this is a security vulnerability, do not file it here — follow + [SECURITY.md](https://github.com/labstack/onebox/blob/main/SECURITY.md) + instead. + + - type: textarea + id: what-happened + attributes: + label: What happened + description: What did Onebox do, and what did you expect it to do instead? + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + description: > + The exact commands you ran, in order. Include the relevant part of your + configuration if the failure depends on it. + placeholder: | + 1. ob plan + 2. ob deploy + 3. ... + validations: + required: true + + - type: textarea + id: output + attributes: + label: Command output + description: > + Paste the output that shows the problem. `ob --output json ` + is often the most useful form. Redact hostnames, secrets, and registry + credentials before pasting. + render: shell + validations: + required: false + + - type: textarea + id: version + attributes: + label: Version + description: Output of `ob version`. + render: shell + validations: + required: true + + - type: input + id: target-host + attributes: + label: Target host + description: > + Linux distribution and version on the server Onebox is managing, plus + the container runtime version if you know it. + placeholder: Ubuntu 24.04, Docker 27.3 + validations: + required: false + + - type: textarea + id: doctor + attributes: + label: ob doctor output + description: > + Optional, but it resolves a large share of environment-related reports + on the first round trip. + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..36d462a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Documentation + url: https://onebox.run + about: Installation, the first deploy, configuration reference, and command output. + - name: Questions and ideas + url: https://github.com/labstack/onebox/discussions + about: Ask how something works, or float an idea before it becomes a proposal. + - name: Security vulnerabilities + url: https://github.com/labstack/onebox/blob/main/SECURITY.md + about: Report privately. Please do not open a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..e41b17d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,53 @@ +name: Feature request +description: Propose a change to what Onebox does. +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Onebox has an explicit, narrow scope — one application, one active + production host — and a documented safety envelope. A change that + widens either is a product decision rather than a code review question, + which is why it is worth settling here before any code exists. + + [`docs/product.md`](https://github.com/labstack/onebox/blob/main/docs/product.md) + records the direction, and + [`/status/capabilities`](https://onebox.run/status/capabilities) records + what the binary does today versus what the schema merely accepts. + + - type: textarea + id: problem + attributes: + label: The problem + description: > + What are you trying to do, and what makes it hard or impossible today? + Describe the situation rather than the solution you have in mind. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed change + description: What should Onebox do instead? Sketch the command or configuration if you have one. + validations: + required: true + + - type: textarea + id: workaround + attributes: + label: Current workaround + description: How are you handling this now, and why is that unsatisfactory? + validations: + required: false + + - type: textarea + id: scope + attributes: + label: Scope and safety + description: > + Does this widen the one-application, one-host scope, or change what + Onebox will do to a running production system without asking? If so, + say what the new boundary should be. + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..834111b5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +## What this changes + + + +Closes # + +## Why this is correct + + + +## Effect on the safety envelope + + + +## Checklist + +- [ ] `just check` passes locally. +- [ ] Tests cover the new behaviour, including the failure paths. +- [ ] Generated documentation is current (`just check` verifies this). +- [ ] I have accepted the [CLA](https://github.com/labstack/onebox/blob/main/CLA.md), or will when the bot asks on my first pull request. diff --git a/README.md b/README.md index 28ad3b3e..f1dce177 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,22 @@ the full rules. [Shipped vs proposed](https://onebox.run/status/capabilities) is the complete account of what the binary executes today and what remains direction. +## How it compares + +Onebox occupies a narrow spot: one application on one Linux server, with a +review gate in front of every change. Neighbouring tools solve overlapping +problems differently, and the difference is usually the boundary rather than +the feature list. + +| If you use | Where Onebox differs | +| --- | --- | +| **Docker Compose** and a few shell scripts | Compose stays the runtime — Onebox generates it, and `ob eject` hands it back permanently. What you gain is the release layer around it: health-gated rolling replacement, recorded predecessors, rollback, and backups. | +| **Kamal** | Both deploy containers over SSH with no agent on the host. Kamal spans multiple hosts and applies when you run it; Onebox is deliberately single-host and puts a digest-bound plan and an explicit approval between you and production. | +| **Dokku**, **CapRover**, **Coolify** | Those run a control plane on the server and lead with git-push or a dashboard. Onebox has no dashboard and nothing resident: a CLI over SSH, a file in your repository, and generated Compose you can read. | +| **Ansible** | Ansible configures hosts in general; you still model application releases yourself. Onebox models only the release — plan, approve, deploy, roll back — and expects you to administer the Linux host underneath it. | +| **Kubernetes** or **k3s** | A cluster reconciles desired state continuously and survives a lost node. Onebox does neither, and says so: no failover, no scheduler. It buys the operational habits — a diff before apply, health gates, recorded releases — without the cluster. | +| **Terraform** | The plan-then-apply ceremony is borrowed on purpose. The subject is different: an application release on one box, not an infrastructure graph across providers. | + ## Built for people and agents The CLI is the interface for both. Every finite machine result uses one diff --git a/docs/media/social-card-mark.svg b/docs/media/social-card-mark.svg new file mode 100644 index 00000000..054d6a84 --- /dev/null +++ b/docs/media/social-card-mark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/media/social-card.typ b/docs/media/social-card.typ new file mode 100644 index 00000000..4b689680 --- /dev/null +++ b/docs/media/social-card.typ @@ -0,0 +1,47 @@ +// The GitHub social preview card, which the documentation site also serves as +// its og:image at /social-card.png. +// +// Render it with IBM Plex Mono on a font path. The face is not installed +// system-wide — the site loads it through @fontsource — so without the path +// the card silently falls back to a proportional face and stops looking like +// the product: +// +// typst compile --font-path --ppi 96 --format png \ +// docs/media/social-card.typ site/public/social-card.png +// +// The page is 960pt x 480pt, which is exactly 1280 x 640 pixels at 96 ppi: the +// size GitHub expects for a social preview, and large enough that a link unfurl +// does not resample it. +#set page(width: 960pt, height: 480pt, margin: (x: 60pt, top: 44pt, bottom: 34pt), fill: rgb("#0d100d")) +#set text(font: "IBM Plex Mono", fill: rgb("#e2ece0")) + +#place(top + left, dx: -60pt, dy: -44pt, rect(width: 960pt, height: 5pt, fill: rgb("#4f9a3c"))) + +#grid( + columns: (72pt, 1fr), + column-gutter: 22pt, + align: horizon, + image("social-card-mark.svg", width: 68pt), + text(size: 56pt, weight: 600, "Onebox"), +) + +#v(34pt) +#text(size: 34pt, weight: 600, fill: rgb("#8fd67a"))[ + Plan-before-apply deploys. \ + Zero downtime. One box. +] + +#v(18pt) +#text(size: 20pt, fill: rgb("#87957f"))[ + Production operations for one application \ + intentionally running on one Linux server. +] + +#place(bottom + left, dy: 0pt, block(width: 840pt)[ + #line(length: 100%, stroke: 0.75pt + rgb("#22291f")) + #v(12pt) + #grid(columns: (1fr, auto), + text(size: 19pt, fill: rgb("#87957f"))[#text(fill: rgb("#4f9a3c"))[\$ ob plan] #h(18pt) sealed diff, then deploy], + text(size: 19pt, fill: rgb("#87957f"))[onebox.run], + ) +]) diff --git a/site/public/social-card.png b/site/public/social-card.png new file mode 100644 index 0000000000000000000000000000000000000000..2a3c9064be8db02acafeaad4b5ccdf123e0afc97 GIT binary patch literal 55829 zcmeFZS5#Bc*Do3r6boQOdKE-KdXp}o^d=>AQ0X11QbG_FMCrXFT{?sss(|z!YUoG_ zJwgZpLc$69Ki_xm80XLbU*zWRAiHmlh;olMv4JHK;{aasu1R(ymq7Mh$`L}Z8zw-Rw>Sz0Z z-^hJ0?T$b1yySl#WCm*!k^Z~u88s2}zt!?<;-G)4ppSO}SN<)@6#@Tq<6lwy|4ckx zoh?^C>+7j=hJEGad{vd7XIGV@1M$4^t~jnos}C+@yZ*u^GQC%VlLO-3(W3TG8$Mqn zdYzm%shQxw!=+lgLCMZlmv?ZXnb7*CExlKY(woJ{tFEL-T^S>TZjG6f~R;(YOq0_&HRs#;3xopmi=boip87qlj@w6tdJnuYyUKsxq%dbx}gtl9S8gw zD75|LFZS;d`p<+%I@%m9=zec@GoHfv*B0hlt(5uye)>H60ol~oKNqAI=Y>5QQB42Y zB7U z`iO2JrIM@n=>7J)OZNc)1YM8oQ_(x%x>B>)LJFtDV8!|Ze z-G45gD*>p9ht70xNm$^|t z7xIVl=6;CE-MBI^t;qs-@jzx+? z)Ulwyf_xw|NJRZzM}YmRU$bs!8|uh+3pI|Q`EoB`jo3Ns-`n22&jq-emtgr&wZE*t(fCePEO8PV^RJ7f&x_?_gH$_cc$dKvdq^h z=!NkMP>-bZvw^4Znm4))K6HNxKhP!)dLxY5BZ;KA7#>FyjnpFK8BF3jTgnOuTNXVwqE6p-`unl_t{U^s&|+y=_ji<<+BLHLg%r| z(u$NUf5`+f5O{*vc(0WH=29PNvg(Uwj}(D5kMH}M4PeU<^-YB`#HVGpZOX>}>I8_PLd^UzY|<|6v5oS5rSY6wqeTkJb^676D!!*^s(i&VS{69tU;dxGCk1?9ZaggJw)*)Jr_eZba`N8y zU^1+m{=?tT0p-E6;xvb51h>)Ia_h4{*GZDwh=wQvXh? znxD7v zRV5?ddaIRtzWYV*WXk+e1w4nNAj^n;Ic@`UXTDGWa)8g*iCz!HGlQFKfw}zUI$VgkmVY2LcUmx;t z5fmckSNsbVP1RW4Le0MYg)N9JhOuvOFy4(DSeh淙VdLPSivUJ4igSgMGYE#f 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%0DgITj5b*gRbf@% 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

Wf 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

I0du|lPc_t(LYFI#F1UH^a=EsGUO;AZl$ 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 h25_yj! + + + + + { markdownHref && ( <> From f2f261aa8f2108db3ca089787a6203ae0f4fa7cf Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 24 Aug 2026 06:04:13 -0700 Subject: [PATCH 2/6] docs: gate the social card instead of trusting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of the first commit found the card carrying two failure modes that nothing would report. The mark was copied into `docs/media/social-card-mark.svg`, byte-identical to the one in `site/public/favicon.svg` and connected to it by nothing. The favicon comment records that four earlier drafts of that mark died before this one; the next revision would have reached the site and left the card — the most-seen surface — drawing a retired logo. Typst reads the favicon directly, so the copy is gone and the render is unchanged, pixel for pixel. The PNG had no generator and no check. `just social-card` renders it, and does the part a hand-typed command cannot: typst warns about an unknown font family and still exits 0, so it verifies the font was actually found and fails when it was not. The typst version is pinned because another release re-lays out every glyph. CI has no font path and so cannot re-render, but `site-build` now asserts the committed PNG exists and matches the dimensions the head advertises — the previous commit emitted `og:image` unconditionally, so renaming the asset would have left every page pointing at a 404. Also corrects a comment from the previous commit: `astro.config.mjs` sets `site` unconditionally, so the fallback is not a configuration this repository ships. Ran `just check`: passes. Exercised `just social-card` against an empty font directory (fails, as intended) and a real one (renders a card identical to the committed PNG), and `check-social-card.mjs` against a missing asset and a wrong-sized one. Co-Authored-By: Claude Opus 5 (1M context) --- CONTRIBUTING.md | 9 +++ Justfile | 47 +++++++++++++ docs/media/social-card-mark.svg | 4 -- docs/media/social-card.typ | 19 +++--- site/package.json | 2 +- site/scripts/check-social-card.mjs | 105 +++++++++++++++++++++++++++++ site/src/components/Head.astro | 7 +- 7 files changed, 176 insertions(+), 17 deletions(-) delete mode 100644 docs/media/social-card-mark.svg create mode 100644 site/scripts/check-social-card.mjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c74467f4..99c6a5ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,6 +87,15 @@ Then commit the result. `just check` fails if the committed pages do not match what the binary produces, and CI fails on any uncommitted change after the gate runs. +The social preview card is generated too, but on a looser contract. +`site/public/social-card.png` comes from `docs/media/social-card.typ` by way of +`just social-card`, which needs IBM Plex Mono on a font path — set +`ONEBOX_FONT_PATH` to a directory holding it. CI has no such directory, so the +gate cannot re-render the card to compare it; what `site-build` does assert is +that the committed PNG exists and still matches the dimensions the site's head +advertises. If you edit the `.typ`, run the recipe and commit the PNG beside it, +because nothing else will notice that the two have drifted apart. + ## Tests New behavior needs a test that fails without the change. Onebox's test suite is diff --git a/Justfile b/Justfile index e8a3b4d5..72daa040 100644 --- a/Justfile +++ b/Justfile @@ -262,6 +262,53 @@ site-build: docs-generate-check # Validate that every generated reference page matches the binary. docs-check: docs-generate-check +# Render the social preview card into site/public. +# +# The card is the repository's GitHub social preview and the site's og:image, so +# it is generated rather than hand-drawn: edit docs/media/social-card.typ and run +# this. It is deliberately not part of `check`. Rendering needs IBM Plex Mono on +# a font path, which CI does not have and this repository does not vendor — the +# site loads the face through @fontsource for the browser, and carrying a copy in +# the tree to draw one image is a poor trade. `site-build` asserts the committed +# PNG is present and correctly sized, which is the part a gate can honestly do. +# +# Typst warns on an unknown font family and still exits 0. A missing face would +# therefore not fail the render; it would quietly ship a card set in a fallback +# face that no longer looks like the product. This reads the warning back and +# fails on it. +# +# The version is pinned because typst's layout changes between releases: another +# one re-renders every glyph position and produces a large diff that is not a +# real change. +social-card: + #!/bin/bash + set -euo pipefail + want_typst="0.15.1" + if ! command -v typst >/dev/null; then + echo "social-card: typst ${want_typst} is required — see https://github.com/typst/typst" >&2; exit 1 + fi + have_typst=$(typst --version | awk '{print $2}') + if [ "$have_typst" != "$want_typst" ]; then + echo "social-card: typst ${have_typst} is installed, but the committed card was rendered with ${want_typst}." >&2 + echo "social-card: another version re-lays out every glyph, so the diff would not be a real change." >&2 + exit 1 + fi + font_path="${ONEBOX_FONT_PATH:-}" + if [ -z "$font_path" ]; then + echo "social-card: set ONEBOX_FONT_PATH to a directory holding IBM Plex Mono (Regular and SemiBold)." >&2 + echo "social-card: the OFL originals are at https://github.com/google/fonts/tree/main/ofl/ibmplexmono" >&2 + exit 1 + fi + render=$(typst compile --root . --font-path "$font_path" --ppi 96 --format png \ + docs/media/social-card.typ site/public/social-card.png 2>&1) + if [ -n "$render" ]; then echo "$render"; fi + if echo "$render" | grep -qi "unknown font family"; then + echo "social-card: typst could not find a font it was asked for, so the card above is set in a fallback face." >&2 + echo "social-card: point ONEBOX_FONT_PATH at IBM Plex Mono and render again." >&2 + exit 1 + fi + echo "rendered site/public/social-card.png" + # Create and publish the next vYYYY.M.REVISION tag from releasable main. release: bash scripts/release.sh diff --git a/docs/media/social-card-mark.svg b/docs/media/social-card-mark.svg deleted file mode 100644 index 054d6a84..00000000 --- a/docs/media/social-card-mark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/media/social-card.typ b/docs/media/social-card.typ index 4b689680..c7bab5c7 100644 --- a/docs/media/social-card.typ +++ b/docs/media/social-card.typ @@ -1,17 +1,18 @@ // The GitHub social preview card, which the documentation site also serves as // its og:image at /social-card.png. // -// Render it with IBM Plex Mono on a font path. The face is not installed -// system-wide — the site loads it through @fontsource — so without the path -// the card silently falls back to a proportional face and stops looking like -// the product: -// -// typst compile --font-path --ppi 96 --format png \ -// docs/media/social-card.typ site/public/social-card.png +// Render it with `just social-card`, which pins the typst version, requires +// IBM Plex Mono on a font path, and fails when the face is missing. Running +// typst by hand does not: it warns about an unknown font family, exits 0, and +// writes a card set in whatever it found instead. // // The page is 960pt x 480pt, which is exactly 1280 x 640 pixels at 96 ppi: the // size GitHub expects for a social preview, and large enough that a link unfurl -// does not resample it. +// does not resample it. `site-build` asserts the committed PNG still matches +// those numbers. +// +// The mark is read from the site's favicon rather than copied here, so a +// revised mark reaches the card the next time it is rendered. #set page(width: 960pt, height: 480pt, margin: (x: 60pt, top: 44pt, bottom: 34pt), fill: rgb("#0d100d")) #set text(font: "IBM Plex Mono", fill: rgb("#e2ece0")) @@ -21,7 +22,7 @@ columns: (72pt, 1fr), column-gutter: 22pt, align: horizon, - image("social-card-mark.svg", width: 68pt), + image("../../site/public/favicon.svg", width: 68pt), text(size: 56pt, weight: 600, "Onebox"), ) diff --git a/site/package.json b/site/package.json index 8a6401eb..25a8e9d8 100644 --- a/site/package.json +++ b/site/package.json @@ -9,7 +9,7 @@ "scripts": { "dev": "astro dev", "start": "astro dev", - "build": "astro check && astro build && node scripts/check-tables.mjs", + "build": "astro check && astro build && node scripts/check-tables.mjs && node scripts/check-social-card.mjs", "preview": "astro preview", "astro": "astro" }, diff --git a/site/scripts/check-social-card.mjs b/site/scripts/check-social-card.mjs new file mode 100644 index 00000000..9b3cb486 --- /dev/null +++ b/site/scripts/check-social-card.mjs @@ -0,0 +1,105 @@ +// Assert that the og:image every page advertises is actually in the build, and +// is the size the same head claims it is. +// +// Starlight emits `twitter:card: summary_large_image` and no image to pair with +// it, so `src/components/Head.astro` supplies one. That tag is written by hand +// against a file in `public/`, and nothing else connects the two: rename or drop +// `social-card.png` and the build still succeeds while every page points a +// crawler at a 404. An unfurl is the one surface nobody on the team looks at, so +// the failure would live in production until an outsider mentioned it. +// +// The dimensions are checked too, because `og:image:width` and `og:image:height` +// are hand-typed. A card re-rendered at another size with the numbers left +// behind makes consumers reserve the wrong box, which is the whole reason those +// tags exist. +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; + +const dist = new URL("../dist/", import.meta.url).pathname; + +async function htmlFiles(dir) { + const found = []; + for (const entry of await readdir(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) found.push(...(await htmlFiles(path))); + else if (entry.name.endsWith(".html")) found.push(path); + } + return found; +} + +function meta(html, attribute, name) { + const pattern = new RegExp( + `]+${attribute}="${name}"[^>]+content="([^"]+)"`, + "i", + ); + return html.match(pattern)?.[1] ?? null; +} + +// The IHDR chunk is the first one in every PNG and holds the dimensions as two +// big-endian 32-bit integers, so the header alone answers this without a decoder. +function pngSize(bytes) { + const signature = "89504e470d0a1a0a"; + if (bytes.subarray(0, 8).toString("hex") !== signature) return null; + if (bytes.subarray(12, 16).toString("ascii") !== "IHDR") return null; + return { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) }; +} + +let pages = []; +try { + pages = await htmlFiles(dist); +} catch (error) { + console.error(`check-social-card: cannot read ${dist} — run \`npm run build\` first`); + console.error(String(error)); + process.exit(1); +} + +const missingTag = []; +const declared = new Map(); +for (const page of pages) { + const html = await readFile(page, "utf8"); + const href = meta(html, "property", "og:image"); + if (href === null) { + missingTag.push(page.replace(dist, "")); + continue; + } + const width = meta(html, "property", "og:image:width"); + const height = meta(html, "property", "og:image:height"); + declared.set(href, { width, height }); +} + +if (missingTag.length > 0) { + console.error("check-social-card: pages built without an og:image, so they unfurl as an empty large card:"); + for (const page of missingTag) console.error(` ${page}`); + process.exit(1); +} + +if (declared.size === 0) { + console.error("check-social-card: the build produced no HTML, which cannot be right"); + process.exit(1); +} + +for (const [href, size] of declared) { + const path = new URL(href).pathname; + let bytes; + try { + bytes = await readFile(join(dist, path)); + } catch { + console.error(`check-social-card: ${href} is advertised by every page but ${path} is not in the build`); + process.exit(1); + } + + const actual = pngSize(bytes); + if (actual === null) { + console.error(`check-social-card: ${path} is not a PNG, so consumers that trust the tag will show nothing`); + process.exit(1); + } + if (String(actual.width) !== size.width || String(actual.height) !== size.height) { + console.error( + `check-social-card: ${path} is ${actual.width}x${actual.height}, but the head declares ${size.width}x${size.height}`, + ); + console.error("check-social-card: re-render with `just social-card`, or correct the tags in src/components/Head.astro"); + process.exit(1); + } +} + +console.log(`check-social-card: ${declared.size} og:image target(s), present and correctly sized`); diff --git a/site/src/components/Head.astro b/site/src/components/Head.astro index 9eafb685..432654a7 100644 --- a/site/src/components/Head.astro +++ b/site/src/components/Head.astro @@ -24,9 +24,10 @@ const markdownHref = ids.has(id) // is what the tagline is for, and a generated-per-page card would be a build // dependency on fonts the site only loads in the browser. // -// Absolute URLs are required here — Open Graph consumers do not resolve -// relative hrefs — so both fall back to the current URL when `site` is unset, -// which only happens in a bare `astro dev`. +// Absolute URLs are required here: Open Graph consumers do not resolve relative +// hrefs. `astro.config.mjs` always sets `site`, so the fallback is not a +// configuration this repository ships — Astro types `Astro.site` as optional +// regardless, and the line above resolves its URL the same way. const socialCardHref = new URL("/social-card.png", Astro.site ?? Astro.url).href; --- From 0fabe158627099e253ab6150ae614151c5d95ca8 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 24 Aug 2026 06:08:14 -0700 Subject: [PATCH 3/6] docs: keep a bad card render out of the tree Two parity fixes with the same change in the Fanout repository, where the review that prompted them ran. `just social-card` wrote straight to `site/public/social-card.png` and only then checked whether typst had fallen back to a substitute face. It failed correctly, but the wrong card was already on disk, and the next person to look would find a card that no recipe claims to have produced. It renders to a temporary file and moves it into place once the render is known good. The footer was placed at a fixed offset out of flow, so a longer headline would have drawn the divider through the body copy with typst reporting nothing. Flexible space pushes it down instead: the overflow spills onto a second page, and a two-page render fails the PNG export outright. Ran `just check`: passes. The card re-renders byte-identically after the footer change, the recipe leaves the committed PNG untouched when the font is missing, and a deliberately overlong headline fails the export. Co-Authored-By: Claude Opus 5 (1M context) --- Justfile | 7 ++++++- docs/media/social-card.typ | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Justfile b/Justfile index 72daa040..1744ba22 100644 --- a/Justfile +++ b/Justfile @@ -299,14 +299,19 @@ social-card: echo "social-card: the OFL originals are at https://github.com/google/fonts/tree/main/ofl/ibmplexmono" >&2 exit 1 fi + # Rendered aside and moved into place only once it is known good, so a run + # that fell back to a substitute face cannot leave that card in the tree. + staged=$(mktemp -t social-card).png + trap 'rm -f "$staged"' EXIT render=$(typst compile --root . --font-path "$font_path" --ppi 96 --format png \ - docs/media/social-card.typ site/public/social-card.png 2>&1) + docs/media/social-card.typ "$staged" 2>&1) if [ -n "$render" ]; then echo "$render"; fi if echo "$render" | grep -qi "unknown font family"; then echo "social-card: typst could not find a font it was asked for, so the card above is set in a fallback face." >&2 echo "social-card: point ONEBOX_FONT_PATH at IBM Plex Mono and render again." >&2 exit 1 fi + mv "$staged" site/public/social-card.png echo "rendered site/public/social-card.png" # Create and publish the next vYYYY.M.REVISION tag from releasable main. diff --git a/docs/media/social-card.typ b/docs/media/social-card.typ index c7bab5c7..6c780c37 100644 --- a/docs/media/social-card.typ +++ b/docs/media/social-card.typ @@ -38,11 +38,15 @@ intentionally running on one Linux server. ] -#place(bottom + left, dy: 0pt, block(width: 840pt)[ - #line(length: 100%, stroke: 0.75pt + rgb("#22291f")) - #v(12pt) - #grid(columns: (1fr, auto), - text(size: 19pt, fill: rgb("#87957f"))[#text(fill: rgb("#4f9a3c"))[\$ ob plan] #h(18pt) sealed diff, then deploy], - text(size: 19pt, fill: rgb("#87957f"))[onebox.run], - ) -]) +// Pushed down by flexible space rather than placed at the bottom out of flow. +// Placed, it drew at a fixed offset however tall the text above had grown, so +// one extra headline line put the divider through the body copy and typst still +// exited 0. In flow, that overflow spills onto a second page instead, and a +// two-page render fails the PNG export outright. +#v(1fr) +#line(length: 100%, stroke: 0.75pt + rgb("#22291f")) +#v(12pt) +#grid(columns: (1fr, auto), + text(size: 19pt, fill: rgb("#87957f"))[#text(fill: rgb("#4f9a3c"))[\$ ob plan] #h(18pt) sealed diff, then deploy], + text(size: 19pt, fill: rgb("#87957f"))[onebox.run], +) From 8d7310a6cde42ffe64bc2ff20c6e309ef020928f Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 24 Aug 2026 06:32:02 -0700 Subject: [PATCH 4/6] docs: neutralise a comment about the team The comment justified the og:image check by saying an unfurl is the surface nobody on the team looks at. That is a claim about colleagues in a public repository, and it is not what makes the check worth having. Says what the failure actually looks like instead. Co-Authored-By: Claude Opus 5 (1M context) --- site/scripts/check-social-card.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/scripts/check-social-card.mjs b/site/scripts/check-social-card.mjs index 9b3cb486..79102a47 100644 --- a/site/scripts/check-social-card.mjs +++ b/site/scripts/check-social-card.mjs @@ -5,8 +5,8 @@ // it, so `src/components/Head.astro` supplies one. That tag is written by hand // against a file in `public/`, and nothing else connects the two: rename or drop // `social-card.png` and the build still succeeds while every page points a -// crawler at a 404. An unfurl is the one surface nobody on the team looks at, so -// the failure would live in production until an outsider mentioned it. +// crawler at a 404 — a failure that shows up in other people's link previews +// long before it shows up in anything this repository builds or serves. // // The dimensions are checked too, because `og:image:width` and `og:image:height` // are hand-typed. A card re-rendered at another size with the numbers left From 44dce5f0b2ec5e46f54a170f4fde77f122857471 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 24 Aug 2026 06:34:35 -0700 Subject: [PATCH 5/6] docs: explain the tagline without naming other vendors The hero comment credited two other companies' landing pages for the decision to name the category in the first clause. The reasoning holds without them, and a public repository is a poor place to record which competitors were studied. Keeps the principle, drops the names. Co-Authored-By: Claude Opus 5 (1M context) --- site/src/content/docs/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index ddec6f71..123e1723 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -31,8 +31,8 @@ head: # The tagline opens by naming the category outright. The previous version led # with the claim — "production operations for an application intentionally # running on one server" — which only lands for a reader who already knows what -# kind of thing this is. Coolify and Resend both name the category in the first -# clause, and it is the single cheapest thing this page was missing. +# kind of thing this is. A landing page is read by people who do not yet, and +# naming the category in the first clause is the cheapest way to tell them. hero: title: Deploys you can read before they happen. tagline: | From ab90755cf8e3a42cd8d2a92d2851f203504b6f48 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 24 Aug 2026 06:40:45 -0700 Subject: [PATCH 6/6] docs: stop the card recipe leaking a temp file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `mktemp -t social-card` reserves a name, and the recipe then appended `.png` to it — so typst wrote to a path nothing had reserved, the reserved file was left behind empty on every run, and the trap cleaned up only the render. A temporary directory reserves the whole name. Also handles a relative `og:image` in the site check. The tag is hand-written and someone could reasonably make it relative; Open Graph consumers do not resolve those, and the check now says so rather than failing on a URL parse error. Ran `just check`: passes. `just social-card` re-renders the committed PNG byte-identically. Co-Authored-By: Claude Opus 5 (1M context) --- Justfile | 8 ++++++-- site/scripts/check-social-card.mjs | 11 ++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Justfile b/Justfile index 1744ba22..81c5f805 100644 --- a/Justfile +++ b/Justfile @@ -301,8 +301,12 @@ social-card: fi # Rendered aside and moved into place only once it is known good, so a run # that fell back to a substitute face cannot leave that card in the tree. - staged=$(mktemp -t social-card).png - trap 'rm -f "$staged"' EXIT + # A directory, not `mktemp -t `: that reserves a name without the .png + # suffix typst needs, so appending one both leaves the reserved file behind + # and writes to a path nothing reserved. + staged_dir=$(mktemp -d) + trap 'rm -rf "$staged_dir"' EXIT + staged="$staged_dir/social-card.png" render=$(typst compile --root . --font-path "$font_path" --ppi 96 --format png \ docs/media/social-card.typ "$staged" 2>&1) if [ -n "$render" ]; then echo "$render"; fi diff --git a/site/scripts/check-social-card.mjs b/site/scripts/check-social-card.mjs index 79102a47..b605be18 100644 --- a/site/scripts/check-social-card.mjs +++ b/site/scripts/check-social-card.mjs @@ -79,7 +79,16 @@ if (declared.size === 0) { } for (const [href, size] of declared) { - const path = new URL(href).pathname; + // The tag is written by hand, so an author could reasonably make it relative. + // Open Graph consumers do not resolve those, and neither does this check — + // saying so beats throwing a URL parse error at whoever runs the build. + let path; + try { + path = new URL(href).pathname; + } catch { + console.error(`check-social-card: og:image is "${href}", which is not an absolute URL — consumers cannot resolve it`); + process.exit(1); + } let bytes; try { bytes = await readFile(join(dist, path));