From a062a2e63ae38c33ce5221c3538a5489a34a72b6 Mon Sep 17 00:00:00 2001 From: w0rxbend Date: Sun, 9 Aug 2026 23:17:24 +0300 Subject: [PATCH 1/3] fix(site): stop two documents racing for the same index.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The published landing page was a corrupt file holding two HTML documents: `` appeared in the middle, followed by the tail of a second copy of the same page. Readers saw the title stamped over itself, raw SVG path coordinates as visible body text, and the sidebar dumped into the page as a bare bullet list. The cause is that two different documents both rendered to `index.html`. Helium's landing page is not a file anyone writes directly: the theme takes the content of a document named `landing-page`, lifts it out of the content tree, and re-inserts it as the *title document* of the root directory — and a directory's title document is what renders to `index.html`. Our own `site/src/index.md` rendered to `index.html` too. Laika renders documents in parallel and nothing arbitrates between two writers to one path, so the outcome was a coin flip. On the deploy that is live now, the shorter landing page was written over the front of the longer page without truncating it, leaving the tail behind — which is why both files are exactly 19188 bytes. Locally the race went the other way and produced a clean file with the landing page silently missing altogether. Both builds exited 0 and reported nothing. So: rename `site/src/index.md` to `site/src/landing-page.md`, leaving exactly one document that renders to `index.html`. Two consequences worth knowing before touching this again: - `homeLink` now points at `Root / "README"`, with no `.md` and no such file in `site/src`. That is the path the re-inserted title document ends up at, `README` being Laika's default title-document input name. `Root / "landing-page.md"` fails link validation, because by the time links resolve that document is gone from the tree; `Root / "README.md"` fails because the re-inserted document carries no suffix. Both were tried. The reasoning is in a comment, since a suffix-less path to a file that does not exist otherwise reads as a typo. - `landing-page.md` is deliberately absent from `laika.navigationOrder`. It is not a sidebar entry, because by then it is not in the tree. --- site/README.md | 2 +- site/build/laika.scala | 19 ++++++++++++++++++- site/src/directory.conf | 5 ++++- site/src/{index.md => landing-page.md} | 0 4 files changed, 23 insertions(+), 3 deletions(-) rename site/src/{index.md => landing-page.md} (100%) diff --git a/site/README.md b/site/README.md index c0c78d9..d7df48a 100644 --- a/site/README.md +++ b/site/README.md @@ -16,7 +16,7 @@ scripts/site.sh --help # the pipeline, step by step | Path | What it is | Owner | | --- | --- | --- | -| `src/index.md` | The landing page. | this directory | +| `src/landing-page.md` | The prose of the front page, rendered below the header and the teasers. The filename is fixed by Helium, and `src/` must hold no `index.md` or `README.md` — see the note above `homeLink` in `build/laika.scala`. | this directory | | `src/getting-started.md` | Install, first request, error rails, pagination. | this directory | | `src/examples.md` | The `modules/examples` programs. Its table is generated at build time. | this directory | | `src/directory.conf` | Laika configuration for the content tree: title and sidebar order. | this directory | diff --git a/site/build/laika.scala b/site/build/laika.scala index aa47436..a20f274 100644 --- a/site/build/laika.scala +++ b/site/build/laika.scala @@ -199,7 +199,24 @@ object Site: .favIcons(Favicon.internal(Root / "assets" / "favicon.svg", sizes = "32x32")) .site .topNavigationBar( - homeLink = IconLink.internal(Root / "index.md", HeliumIcon.home), + // `Root / "README"`, with no `.md` and no such file in site/src, is not a typo. + // + // MEASURED, NOT ASSUMED. The landing page configured below is not a page anyone writes directly. Helium takes + // the content of `site/src/landing-page.md`, lifts that document out of the content tree, and re-inserts it as + // the *title document* of the root directory — at the suffix-less path `/README`, because `README` is Laika's + // default title-document input name. A directory's title document is what renders to `index.html`, which is how + // the landing page comes to be the site's front page. + // + // So this is the only path that resolves to the front page. `Root / "landing-page.md"` fails link validation, + // because by the time links are resolved that document is gone from the tree. `Root / "README.md"` fails too: + // the re-inserted document carries no suffix. + // + // The consequence to keep in mind is that site/src must contain no `index.md` and no `README.md`. Either one + // would also render to `index.html`, and Laika renders documents in parallel — so the two writers race for the + // same file. That is not hypothetical: it is what produced a published `index.html` holding the landing page + // spliced on top of the tail of a second, differently-templated copy of the same page. scripts/site.sh fails + // the build if either file reappears. + homeLink = IconLink.internal(Root / "README", HeliumIcon.home), navLinks = Seq( TextLink.internal(Root / "getting-started.md", "Getting Started"), TextLink.internal(Root / "examples.md", "Examples"), diff --git a/site/src/directory.conf b/site/src/directory.conf index 64ab811..9494952 100644 --- a/site/src/directory.conf +++ b/site/src/directory.conf @@ -14,8 +14,11 @@ laika.title = codeberg4s # scripts/site.sh. If one of them is missing, Laika ignores the entry rather # than failing, and scripts/site.sh prints a warning naming what it generated in # its place. +# +# `landing-page.md` is deliberately absent from this list. Helium lifts that file +# out of the content tree and renders it as the site's landing page, so it is not +# a sidebar entry — see the note above `homeLink` in site/build/laika.scala. laika.navigationOrder = [ - index.md getting-started.md guides reference diff --git a/site/src/index.md b/site/src/landing-page.md similarity index 100% rename from site/src/index.md rename to site/src/landing-page.md From d5a0d2b1d9904f18e91eb66c01e168ef35ed1e37 Mon Sep 17 00:00:00 2001 From: w0rxbend Date: Sun, 9 Aug 2026 23:17:43 +0300 Subject: [PATCH 2/3] style(site): drop the landing page heading that repeats the header The landing page rendered "codeberg4s" twice in a row: once as the theme's own header, at 48px on the blue gradient, and then again immediately underneath as an `

` from the Markdown source. That was invisible while `index.md` was a normal page, because a page needs a title of its own. It became visible the moment the file started being rendered underneath a header that already carries the title. The heading is removed rather than reworded. The document's title comes from `laika.title` in `site/src/directory.conf`, and the browser tab title from the site metadata, so nothing depended on it. --- site/build/laika.scala | 3 +++ site/src/landing-page.md | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/site/build/laika.scala b/site/build/laika.scala index a20f274..332d972 100644 --- a/site/build/laika.scala +++ b/site/build/laika.scala @@ -229,6 +229,9 @@ object Site: .site .pageNavigation(depth = 3) + // Everything below the header and the teaser rows is the prose of `site/src/landing-page.md`. That file has no + // top-level heading of its own on purpose: `title` here already renders "codeberg4s" at 48px directly above it, + // and a second `

codeberg4s

` under it said the same word twice. withChrome.site .landingPage( title = Some(SiteInfo.title), diff --git a/site/src/landing-page.md b/site/src/landing-page.md index b375050..ed97982 100644 --- a/site/src/landing-page.md +++ b/site/src/landing-page.md @@ -1,5 +1,3 @@ -# codeberg4s - A Scala 3 client for the [Codeberg](https://codeberg.org) and [Forgejo](https://forgejo.org) REST API v1. All 439 in-scope operations are implemented, on both error rails, against models built from captured responses From 9d7fa0935f2ed79e8a82c82710c2cd81988d5e33 Mon Sep 17 00:00:00 2001 From: w0rxbend Date: Sun, 9 Aug 2026 23:17:56 +0300 Subject: [PATCH 3/3] build(site): fail the build when a second document claims index.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two-writers-to-index.html bug that produced a corrupt published landing page was silent from end to end: Laika reported nothing, the script exited 0, and the workflow's existing guard — "does out/site/html/index.html exist?" — was satisfied by the corrupt file. Nothing stops the same mistake being made again. Adding `index.md` back to `site/src`, or adding a `README.md` there, reintroduces it exactly, and neither filename looks wrong: both are the obvious name for a directory's front page in every other context. So check for them while staging, before mdoc runs, and fail with a message that names the file and says where its content belongs. Costs one loop over two filenames; the alternative is finding out from a reader. --- scripts/site.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/site.sh b/scripts/site.sh index b5d15ca..149f8cd 100755 --- a/scripts/site.sh +++ b/scripts/site.sh @@ -173,6 +173,26 @@ if [[ -d "$ASSETS" ]]; then cp -R "$ASSETS/." "$STAGED/assets/" fi +# The landing page is not a file anyone writes directly. Helium takes the content +# of site/src/landing-page.md, lifts that document out of the tree and re-inserts +# it as the root directory's *title document* — and a title document is what +# renders to index.html. +# +# So a root index.md or README.md is not merely redundant, it is a second writer +# to index.html. Laika renders documents in parallel, and nothing in Laika or in +# this script arbitrates between them: whichever finishes last wins, and if they +# overlap the file ends up holding both. That is not hypothetical. The published +# site once served an index.html that was the landing page written over the first +# half of a second, differently-templated copy of the same page — closing +# in the middle, the sidebar's link list dumped into the body +# below it. Both builds exited 0. +# +# Fail here instead, naming the file and where its content belongs. +for collision in index.md README.md; do + [[ -f "$STAGED/$collision" ]] || continue + die "site/src/$collision renders to index.html, and so does the generated landing page. Two writers, one file, no arbitration — see the note above homeLink in site/build/laika.scala. Put the front page's prose in site/src/landing-page.md instead." +done + # MEASURED, NOT ASSUMED: Laika 1.3.2 does not resolve a link target written # `./sibling.md` — it reads `.` as a path segment and reports "unresolved # internal reference". `sibling.md` resolves; `../other/page.md` resolves.