From b6afdf1ecce34f3d5273469dd628bc707ae2d8c9 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 11:12:23 +0100 Subject: [PATCH 1/6] Push auto-release tags as renovate-bot so release.yml fires, and give the Docker Hub page its short description --- .gitea/workflows/release.yml | 3 ++- .gitea/workflows/renovate.yml | 3 +++ AGENTS.md | 9 +++++---- CHANGELOG.md | 6 ++++++ README.md | 17 +++++++++++++--- docs/reading-cost.md | 17 ++++++++++++++++ release-tooling/auto-release.test.ts | 10 ++++++++++ release-tooling/dockerhub-overview.test.ts | 23 +++++++++++++++++++++- release-tooling/dockerhub-overview.ts | 10 ++++++---- todo.md | 12 ----------- 10 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 release-tooling/auto-release.test.ts diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 4f785e3..7dd707f 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -81,7 +81,8 @@ jobs: - uses: actions/checkout@v7.0.1 if: github.event_name == 'push' # Publish the named release's own tree, so the page never pairs one Plainpages tag with another - # release's sidecar pins. A version that was never released fails here. + # release's sidecar pins, which is also why it never syncs on a push to main. A version that + # was never released fails here. - uses: actions/checkout@v7.0.1 if: github.event_name == 'workflow_dispatch' with: diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml index b05e8f3..fffd8b7 100644 --- a/.gitea/workflows/renovate.yml +++ b/.gitea/workflows/renovate.yml @@ -38,6 +38,9 @@ jobs: - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 + # A persisted token rides every request as an extraheader, outranking the PAT in the push + # URL: Gitea then sees the Actions bot push the tag and starts no workflow for it. + persist-credentials: false - name: Tag a release for what Renovate merged env: RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index ab3d861..cd48e46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -340,10 +340,11 @@ Revisit only if the stated reason stops holding. *text* guard — the test image ships neither `git` nor `bash`. This is why the Docker Hub overview is `release-tooling/dockerhub-overview.md.tmpl` and not a `.md`: a release reads it and a unit test guards it, so giving it a `.md` name would let a broken `{{VERSION}}` merge with its own guard - skipped. `README.md` is the one markdown a test reads — `release-tooling/contract-version.test.ts` - checks its `apiVersion` samples — and a README-only change skips that check; accepted, because - those samples are illustrative and the copies that matter (`examples/`, `views/`, the template) are - gated. **Valid while no markdown file is rendered or executed.** + skipped. `README.md` is the one markdown tests read — `release-tooling/contract-version.test.ts` + checks its `apiVersion` samples, `release-tooling/dockerhub-overview.test.ts` that the Hub page + says only what it says — and a README-only change skips both; accepted, because those samples are + illustrative, the copies that matter (`examples/`, `views/`, the template) are gated, and the next + gated change catches a README that dropped a Hub command. **Valid while no markdown file is rendered or executed.** - **CI docker logins share the runner host's Docker config.** The act_runner is host-mode, so `docker login`/`logout` in the workflows mutate one shared `~/.docker/config.json`: concurrent jobs can race (one job's logout can 401 another's push — recover by re-running), and tokens sit in that diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d177e..0372f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ at boot. Entries start at 0.3.0. ## Unreleased +### Every release reaches Docker Hub, and the Hub page has a short description + +Releases 0.4.1 to 0.4.20 were tagged in git but never published to Docker Hub or as semver tags in +the Gitea registry; they are now, and every later release is. The Hub page gains a one-line +description. + ### The admin example stops an administrator locking themselves out through a group On the Groups screen, unticking a permission on a group you are in, removing yourself (or a nested diff --git a/README.md b/README.md index dadb06e..1ed026d 100644 --- a/README.md +++ b/README.md @@ -1591,10 +1591,11 @@ tags sync to [Docker Hub](https://hub.docker.com/r/larvit/plainpages), releases The [contract check](#contract-versioning) guards the tag before anything is published, refusing one whose `major.minor` disagrees with `HOST_API_VERSION` and naming the value to set. -**The Docker Hub overview** is published by a separate `publish-overview` job from -[`release-tooling/dockerhub-overview.md.tmpl`](release-tooling/dockerhub-overview.md.tmpl), with +**The Docker Hub overview** and short description are published by a separate `publish-overview` +job from [`release-tooling/dockerhub-overview.md.tmpl`](release-tooling/dockerhub-overview.md.tmpl), with `{{VERSION}}` rendered to the release, so the Plainpages tag it tells adopters to pull cannot go -stale. Its sidecar pins are Renovate-managed and gated against this repo's own compose files, so the +stale. The description must open a sentence of this README, and every command the overview shows +must appear here too. Its sidecar pins are Renovate-managed and gated against this repo's own compose files, so the quick start stays a topology CI has actually run. It is its own job for two reasons: the images are already pushed and irreversible by then, so a Hub outage leaves the promotion green and the images untouched; and the page has its own door — run the @@ -1666,6 +1667,16 @@ the one-shot bootstrap) — and mounts no source. Secrets come from the environm running insecure. Before going live, supply the production secrets and any SSO credentials — the **only** manual prep ([What you must supply](#what-you-must-supply-the-only-manual-prep)). +**From the published image**, without a clone: save the `compose.yml` the +[Docker Hub page](https://hub.docker.com/r/larvit/plainpages) shows, then extract the Ory config the +image ships and start, with `X.Y.Z` the release you deploy: + +```bash +docker run --rm larvit/plainpages:X.Y.Z tar -cf - ory | tar -xf - +mkdir -p plugins +docker compose up -d +``` + **Back up the `pgdata` volume.** Once a plugin declares [storage](#plugin-storage), Postgres holds business data that exists nowhere else, alongside Ory's identities — the stack stops being reproducible from the image and config alone. Snapshot the volume, or `pg_dump` each database on a diff --git a/docs/reading-cost.md b/docs/reading-cost.md index 8e27531..e8ce3f5 100644 --- a/docs/reading-cost.md +++ b/docs/reading-cost.md @@ -192,3 +192,20 @@ Read most (opens and searches): - `ci.sh` 1 - `examples/plugins/admin/admin-clients.test.ts` 1 - `examples/plugins/admin/admin-grants.test.ts` 1 + +## Reading cost, chunk 4a8c9037acde, finished 2026-10-31T10:07:56Z + +126616 new input tokens before the first commit. + +Read most (opens and searches): + +- `src/http/app-admin.test.ts` 7 +- `examples/plugins/admin/admin-groups.ts` 4 +- `README.md` 3 +- `todo.md` 3 +- `AGENTS.md` 2 +- `examples/plugins/admin/README.md` 2 +- `examples/plugins/admin/admin-grants.ts` 2 +- `examples/plugins/admin/i18n/en-US.ts` 2 +- `CHANGELOG.md` 1 +- `examples/plugins/admin/admin-groups.test.ts` 1 diff --git a/release-tooling/auto-release.test.ts b/release-tooling/auto-release.test.ts new file mode 100644 index 0000000..c44c605 --- /dev/null +++ b/release-tooling/auto-release.test.ts @@ -0,0 +1,10 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; + +test("auto-release's checkout persists no credentials, so the tag push authenticates as renovate-bot", () => { + const workflow = readFileSync(".gitea/workflows/renovate.yml", "utf8"); + const job = workflow.slice(workflow.indexOf("\n auto-release:")); + const checkout = job.slice(job.indexOf("actions/checkout@"), job.indexOf("\n - name:")); + assert.match(checkout, /^\s+persist-credentials: false$/m); +}); diff --git a/release-tooling/dockerhub-overview.test.ts b/release-tooling/dockerhub-overview.test.ts index f0153b0..a314f78 100644 --- a/release-tooling/dockerhub-overview.test.ts +++ b/release-tooling/dockerhub-overview.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; -import { jwtFrom, leftoverPlaceholders, renderOverview } from "./dockerhub-overview.ts"; +import { DESCRIPTION, jwtFrom, leftoverPlaceholders, renderOverview } from "./dockerhub-overview.ts"; const TEMPLATE = "release-tooling/dockerhub-overview.md.tmpl"; const template = () => readFileSync(TEMPLATE, "utf8"); @@ -46,3 +46,24 @@ test("jwtFrom accepts only a non-empty string token, never throwing on a hostile assert.equal(jwtFrom({ token: "" }), null); assert.equal(jwtFrom({ token: 42 }), null); }); + +test("the short description opens a README sentence and fits Docker Hub's 100 characters", () => { + const readme = readFileSync("README.md", "utf8").replaceAll("**", ""); + assert.ok(DESCRIPTION.length <= 100, `${DESCRIPTION.length} characters`); + const escaped = DESCRIPTION.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + assert.match(readme, new RegExp(`^${escaped}(?=[\\s.:,])`, "m")); +}); + +test("every command the overview shows, the README shows too", () => { + const commands = (source: string) => + [...source.matchAll(/```bash\n(.*?)```/gs)].flatMap((block) => + (block[1] ?? "") + .split("\n") + .map((line) => (line.split(" #")[0] ?? "").trim().replace(/larvit\/plainpages:\S+/g, "larvit/plainpages:X.Y.Z")) + .filter((line) => line !== ""), + ); + const shown = new Set(commands(readFileSync("README.md", "utf8"))); + const published = commands(template()); + assert.ok(published.length > 0, `${TEMPLATE} should show commands`); + for (const command of published) assert.ok(shown.has(command), `${TEMPLATE} runs ${command}, which README.md doesn't show`); +}); diff --git a/release-tooling/dockerhub-overview.ts b/release-tooling/dockerhub-overview.ts index f637c99..7a7b8a4 100644 --- a/release-tooling/dockerhub-overview.ts +++ b/release-tooling/dockerhub-overview.ts @@ -1,5 +1,5 @@ // Publishes the Docker Hub repository overview from dockerhub-overview.md.tmpl, rendering -// `{{VERSION}}` to the release being published. +// `{{VERSION}}` to the release being published, and the short description. import { readFileSync } from "node:fs"; import { join } from "node:path"; @@ -8,6 +8,8 @@ const HUB = "https://hub.docker.com/v2"; const TIMEOUT_MS = 30_000; const VERSION = /^\d+\.\d+\.\d+$/; +export const DESCRIPTION = "A self-hostable foundation for server-rendered web applications"; + export function renderOverview(source: string, version: string): string { return source.replaceAll("{{VERSION}}", version); } @@ -81,21 +83,21 @@ async function main(): Promise { if (!jwt) return fail("Docker Hub login returned no token"); const res = await post(`${HUB}/repositories/${repo}/`, { - body: JSON.stringify({ full_description: body }), + body: JSON.stringify({ description: DESCRIPTION, full_description: body }), headers: { authorization: `Bearer ${jwt}`, "content-type": "application/json" }, method: "PATCH", }); if ("error" in res) return fail(`Docker Hub unreachable: ${res.error}`); if (!res.ok) { return fail( - `Docker Hub overview PATCH failed: ${res.status} ${res.text}` + + `Docker Hub description and overview PATCH failed: ${res.status} ${res.text}` + (res.status === 403 ? "\n403 means DOCKERHUB_TOKEN lacks the delete scope — editing the overview needs " + "read/write/delete, which pushing images alone does not (README -> CI/CD)." : ""), ); } - process.stdout.write(`Docker Hub overview updated for ${repo} at ${version}\n`); + process.stdout.write(`Docker Hub description and overview updated for ${repo} at ${version}\n`); return 0; } diff --git a/todo.md b/todo.md index 286339b..10c04d5 100644 --- a/todo.md +++ b/todo.md @@ -12,7 +12,6 @@ | ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score | |---|---|---|---|---|---|---|---|---|---|---|---| -| 1 | 0.4.21 | | owner | **Publish every release to Docker Hub, and give the Hub page its short description.** | 3 | 3 | 2 | 6 | 5 | 1 by 3 | 1.3 | | 32 | 0.4.21 | | | **Fail the gate on a stylesheet feature the browser floor lacks, unless it is guarded.** | 4 | 2 | 2 | 6 | 7 | code health by 2 | 1.1 | | 13 | 0.4.21 | | | **Accept a verification code pasted with surrounding whitespace.** | 4 | 2 | 3 | 7 | 7 | 2 by 8 | 1.0 | | 19 | 0.5.0 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 | @@ -28,17 +27,6 @@ ## Details -### 1. Publish every release to Docker Hub, and give the Hub page its short description. - -Hub's newest tag is 0.4.0, while git has v0.4.1 onwards, and the overview still says 0.4.0. No -`release.yml` run exists for the tags auto-release pushes as `renovate-bot`; find out why Gitea -doesn't fire `push: tags` for them, fix it, and backfill the missing tags. fejkdata did this -(`../fejkdata`, commits `51d924a` and `34c8234`), and two parts port directly: a one-line -description riding the existing PATCH as its `description` field, and a unit test that the -description opens a README sentence and every command the overview shows appears in the README. -fejkdata also syncs the page on any push to `main`, with its own token in an environment only `main` -may use; decide whether that fits here, where `{{VERSION}}` must render a released tag. - ### 32. Fail the gate on a stylesheet feature the browser floor lacks, unless it is guarded. Nothing checks README → "Two browser floors", and no E2E engine is old enough to show a break. -- 2.52.0 From 1adeb776a242eb3723c0d2bb9bc135ee326526a2 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 11:13:39 +0100 Subject: [PATCH 2/6] =?UTF-8?q?Answer=20security=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=200=20=F0=9F=9F=A1=20(0=20misread);=20clean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.52.0 From 282c4b17da694d1b0a6f43b80196144776985b99 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 11:19:11 +0100 Subject: [PATCH 3/6] =?UTF-8?q?Answer=20stability=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=203=20=F0=9F=9F=A1=20(0=20misread);=20the=20descr?= =?UTF-8?q?iption=20test=20refuses=20an=20empty=20one=20and=20needs=20a=20?= =?UTF-8?q?sentence=20start,=20the=20PATCH=20body=20is=20a=20tested=20pure?= =?UTF-8?q?=20function,=20the=20trial-stack=20block=20moves=20to=20Quick?= =?UTF-8?q?=20start?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release-tooling/auto-release.test.ts | 2 +- release-tooling/dockerhub-overview.test.ts | 12 ++++++++---- release-tooling/dockerhub-overview.ts | 9 +++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/release-tooling/auto-release.test.ts b/release-tooling/auto-release.test.ts index c44c605..7c8f6e2 100644 --- a/release-tooling/auto-release.test.ts +++ b/release-tooling/auto-release.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; -test("auto-release's checkout persists no credentials, so the tag push authenticates as renovate-bot", () => { +test("auto-release's checkout doesn't persist credentials, so the tag push authenticates as renovate-bot", () => { const workflow = readFileSync(".gitea/workflows/renovate.yml", "utf8"); const job = workflow.slice(workflow.indexOf("\n auto-release:")); const checkout = job.slice(job.indexOf("actions/checkout@"), job.indexOf("\n - name:")); diff --git a/release-tooling/dockerhub-overview.test.ts b/release-tooling/dockerhub-overview.test.ts index a314f78..2763c4f 100644 --- a/release-tooling/dockerhub-overview.test.ts +++ b/release-tooling/dockerhub-overview.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; -import { DESCRIPTION, jwtFrom, leftoverPlaceholders, renderOverview } from "./dockerhub-overview.ts"; +import { DESCRIPTION, hubPatch, jwtFrom, leftoverPlaceholders, renderOverview } from "./dockerhub-overview.ts"; const TEMPLATE = "release-tooling/dockerhub-overview.md.tmpl"; const template = () => readFileSync(TEMPLATE, "utf8"); @@ -49,9 +49,9 @@ test("jwtFrom accepts only a non-empty string token, never throwing on a hostile test("the short description opens a README sentence and fits Docker Hub's 100 characters", () => { const readme = readFileSync("README.md", "utf8").replaceAll("**", ""); - assert.ok(DESCRIPTION.length <= 100, `${DESCRIPTION.length} characters`); + assert.ok(DESCRIPTION.trim() !== "" && DESCRIPTION.length <= 100, `${DESCRIPTION.length} characters`); const escaped = DESCRIPTION.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - assert.match(readme, new RegExp(`^${escaped}(?=[\\s.:,])`, "m")); + assert.match(readme, new RegExp(`(?:\\n\\n|\\.\\s)${escaped}(?=[\\s.:,])`)); }); test("every command the overview shows, the README shows too", () => { @@ -65,5 +65,9 @@ test("every command the overview shows, the README shows too", () => { const shown = new Set(commands(readFileSync("README.md", "utf8"))); const published = commands(template()); assert.ok(published.length > 0, `${TEMPLATE} should show commands`); - for (const command of published) assert.ok(shown.has(command), `${TEMPLATE} runs ${command}, which README.md doesn't show`); + for (const command of published) assert.ok(shown.has(command), `${TEMPLATE} shows ${command}, which README.md doesn't`); +}); + +test("the PATCH carries the short description beside the overview", () => { + assert.deepEqual(hubPatch("# page"), { description: DESCRIPTION, full_description: "# page" }); }); diff --git a/release-tooling/dockerhub-overview.ts b/release-tooling/dockerhub-overview.ts index 7a7b8a4..5f95e40 100644 --- a/release-tooling/dockerhub-overview.ts +++ b/release-tooling/dockerhub-overview.ts @@ -1,6 +1,3 @@ -// Publishes the Docker Hub repository overview from dockerhub-overview.md.tmpl, rendering -// `{{VERSION}}` to the release being published, and the short description. - import { readFileSync } from "node:fs"; import { join } from "node:path"; @@ -10,6 +7,10 @@ const VERSION = /^\d+\.\d+\.\d+$/; export const DESCRIPTION = "A self-hostable foundation for server-rendered web applications"; +export function hubPatch(overview: string): { description: string; full_description: string } { + return { description: DESCRIPTION, full_description: overview }; +} + export function renderOverview(source: string, version: string): string { return source.replaceAll("{{VERSION}}", version); } @@ -83,7 +84,7 @@ async function main(): Promise { if (!jwt) return fail("Docker Hub login returned no token"); const res = await post(`${HUB}/repositories/${repo}/`, { - body: JSON.stringify({ description: DESCRIPTION, full_description: body }), + body: JSON.stringify(hubPatch(body)), headers: { authorization: `Bearer ${jwt}`, "content-type": "application/json" }, method: "PATCH", }); -- 2.52.0 From ee4d09df0662b39e85e0dc861eb736ad19647b70 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 11:19:11 +0100 Subject: [PATCH 4/6] =?UTF-8?q?Answer=20product-owner=20round=201:=201=20?= =?UTF-8?q?=F0=9F=94=B4,=201=20=F0=9F=9F=A1=20(0=20misread);=20the=20image?= =?UTF-8?q?-only=20start=20is=20a=20Quick=20start=20trial=20path,=20pinned?= =?UTF-8?q?=20to=20the=20release=20the=20Hub=20page=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1ed026d..01e8321 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,16 @@ cd plainpages docker compose up -d # http://localhost:3000, live-reloads on source changes ``` +To try it without a clone, save the `compose.yml` the +[Docker Hub page](https://hub.docker.com/r/larvit/plainpages) shows. Then extract the Ory config +the image ships and start, with `X.Y.Z` the release that page names: + +```bash +docker run --rm larvit/plainpages:X.Y.Z tar -cf - ory | tar -xf - +mkdir -p plugins +docker compose up -d +``` + **2. Sign in.** Open and sign in as the seeded admin — **`admin@plainpages.local` / `admin`**. @@ -1591,11 +1601,10 @@ tags sync to [Docker Hub](https://hub.docker.com/r/larvit/plainpages), releases The [contract check](#contract-versioning) guards the tag before anything is published, refusing one whose `major.minor` disagrees with `HOST_API_VERSION` and naming the value to set. -**The Docker Hub overview** and short description are published by a separate `publish-overview` -job from [`release-tooling/dockerhub-overview.md.tmpl`](release-tooling/dockerhub-overview.md.tmpl), with +**The Docker Hub overview** is published by a separate `publish-overview` job from +[`release-tooling/dockerhub-overview.md.tmpl`](release-tooling/dockerhub-overview.md.tmpl), with `{{VERSION}}` rendered to the release, so the Plainpages tag it tells adopters to pull cannot go -stale. The description must open a sentence of this README, and every command the overview shows -must appear here too. Its sidecar pins are Renovate-managed and gated against this repo's own compose files, so the +stale. The same job sets the page's short description. Its sidecar pins are Renovate-managed and gated against this repo's own compose files, so the quick start stays a topology CI has actually run. It is its own job for two reasons: the images are already pushed and irreversible by then, so a Hub outage leaves the promotion green and the images untouched; and the page has its own door — run the @@ -1667,16 +1676,6 @@ the one-shot bootstrap) — and mounts no source. Secrets come from the environm running insecure. Before going live, supply the production secrets and any SSO credentials — the **only** manual prep ([What you must supply](#what-you-must-supply-the-only-manual-prep)). -**From the published image**, without a clone: save the `compose.yml` the -[Docker Hub page](https://hub.docker.com/r/larvit/plainpages) shows, then extract the Ory config the -image ships and start, with `X.Y.Z` the release you deploy: - -```bash -docker run --rm larvit/plainpages:X.Y.Z tar -cf - ory | tar -xf - -mkdir -p plugins -docker compose up -d -``` - **Back up the `pgdata` volume.** Once a plugin declares [storage](#plugin-storage), Postgres holds business data that exists nowhere else, alongside Ory's identities — the stack stops being reproducible from the image and config alone. Snapshot the volume, or `pg_dump` each database on a -- 2.52.0 From a7781869fde2eb0b59d311cd21d16e8d66a4a73e Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 11:19:11 +0100 Subject: [PATCH 5/6] =?UTF-8?q?Answer=20prose=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=206=20=F0=9F=9F=A1=20(0=20misread);=20the=20never?= =?UTF-8?q?-on-main=20reason=20sits=20at=20on:,=20the=20overview=20paragra?= =?UTF-8?q?ph=20stops=20narrating=20its=20test,=20the=20AGENTS=20bullet=20?= =?UTF-8?q?splits=20into=20sentences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/release.yml | 4 ++-- .gitea/workflows/renovate.yml | 3 +-- AGENTS.md | 9 ++++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 7dd707f..0ca879a 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,4 +1,5 @@ name: Release +# The Hub overview names a released tag, so it is published here only, never on a push to main. on: push: tags: ['v[0-9]+.[0-9]+.[0-9]+'] @@ -81,8 +82,7 @@ jobs: - uses: actions/checkout@v7.0.1 if: github.event_name == 'push' # Publish the named release's own tree, so the page never pairs one Plainpages tag with another - # release's sidecar pins, which is also why it never syncs on a push to main. A version that - # was never released fails here. + # release's sidecar pins. A version that was never released fails here. - uses: actions/checkout@v7.0.1 if: github.event_name == 'workflow_dispatch' with: diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml index fffd8b7..d842fcf 100644 --- a/.gitea/workflows/renovate.yml +++ b/.gitea/workflows/renovate.yml @@ -38,8 +38,7 @@ jobs: - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - # A persisted token rides every request as an extraheader, outranking the PAT in the push - # URL: Gitea then sees the Actions bot push the tag and starts no workflow for it. + # A persisted token outranks the PAT in the push URL, and Gitea runs nothing for its pushes. persist-credentials: false - name: Tag a release for what Renovate merged env: diff --git a/AGENTS.md b/AGENTS.md index cd48e46..f5287df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -340,11 +340,10 @@ Revisit only if the stated reason stops holding. *text* guard — the test image ships neither `git` nor `bash`. This is why the Docker Hub overview is `release-tooling/dockerhub-overview.md.tmpl` and not a `.md`: a release reads it and a unit test guards it, so giving it a `.md` name would let a broken `{{VERSION}}` merge with its own guard - skipped. `README.md` is the one markdown tests read — `release-tooling/contract-version.test.ts` - checks its `apiVersion` samples, `release-tooling/dockerhub-overview.test.ts` that the Hub page - says only what it says — and a README-only change skips both; accepted, because those samples are - illustrative, the copies that matter (`examples/`, `views/`, the template) are gated, and the next - gated change catches a README that dropped a Hub command. **Valid while no markdown file is rendered or executed.** + skipped. `README.md` is the one markdown tests read, and a README-only change skips them. Accepted: + the `apiVersion` samples `release-tooling/contract-version.test.ts` checks are illustrative, and + the copies that matter (`examples/`, `views/`, the template) are gated. A Hub page command the + README dropped (`release-tooling/dockerhub-overview.test.ts`) fails the next gated change. **Valid while no markdown file is rendered or executed.** - **CI docker logins share the runner host's Docker config.** The act_runner is host-mode, so `docker login`/`logout` in the workflows mutate one shared `~/.docker/config.json`: concurrent jobs can race (one job's logout can 401 another's push — recover by re-running), and tokens sit in that -- 2.52.0 From cf8fa437cd1952e345f19336ba58834526018cb5 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 11:20:31 +0100 Subject: [PATCH 6/6] =?UTF-8?q?Answer=20product-owner=20round=202:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=201=20=F0=9F=9F=A1=20(0=20misread);=20the=20no-cl?= =?UTF-8?q?one=20trial=20path=20closes=20Quick=20start=20instead=20of=20in?= =?UTF-8?q?terrupting=20the=20clone-only=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 01e8321..c05f5e6 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,6 @@ cd plainpages docker compose up -d # http://localhost:3000, live-reloads on source changes ``` -To try it without a clone, save the `compose.yml` the -[Docker Hub page](https://hub.docker.com/r/larvit/plainpages) shows. Then extract the Ory config -the image ships and start, with `X.Y.Z` the release that page names: - -```bash -docker run --rm larvit/plainpages:X.Y.Z tar -cf - ory | tar -xf - -mkdir -p plugins -docker compose up -d -``` - **2. Sign in.** Open and sign in as the seeded admin — **`admin@plainpages.local` / `admin`**. @@ -78,6 +68,16 @@ From here, render real pages against the app shell and fetch upstream data — s [Building plugins](#building-plugins) and the runnable reference in [`examples/plugins/scheduling/`](examples/plugins/scheduling/). +**Without a clone**, the image runs a trial stack on its own. Save the `compose.yml` the +[Docker Hub page](https://hub.docker.com/r/larvit/plainpages) shows, then extract the Ory config the +image ships and start, with `X.Y.Z` the release that page names. The page goes on to a first plugin: + +```bash +docker run --rm larvit/plainpages:X.Y.Z tar -cf - ory | tar -xf - +mkdir -p plugins +docker compose up -d +``` + ## Contents - [Goals](#goals) -- 2.52.0