The post layer is undeclared, headless, undated, and unsubscribable #125

Open
opened 2026-08-25 04:10:35 +00:00 by coilyco-ops · 2 comments
Collaborator

Work order for the Frontend Design Engineer seat. Self-contained, final state only. Measured against the live site 2026-08-25.

#123 is the record for the earlier metadata pass and should not be worked from: four of its five items are already done and a cold reader would redo them. This issue is the operative one.

Scope is the post layer only. The top-level pages are in good shape and need no changes. Everything below is one layout plus the sitemap generator.

Already correct, do not touch

One canonical host on www, og:image everywhere, Person JSON-LD on the homepage, unique titles and descriptions on top-level pages, one h1 each. Posts already carry unique <title>, meta description, rel="canonical", and og:image.

1. The sitemap omits every post

sitemap.xml declares four URLs: /, /about/, /hiring/, /resume/. /writing/ and every /posts/<slug>/ are absent.

8871872 described a derived sitemap, but the derivation walks the pages directory only and does not reach the posts collection. Derive from all routes so a new post can never be silently undeclared. This is the highest-value item here.

2. Posts render no h1

/posts/deleting-the-mechanical-scorer/, /posts/stochastic-design-iteration/, and /posts/on-permissions-models-for-cloud-platform-providers/ all have zero h1 elements and start at h2. The post title exists only in <title>.

Render the post title as exactly one h1, and keep body headings at h2 and below so the hierarchy is contiguous.

Note for context: Bing flagged /about/ for a missing h1. That flag is wrong, /about/ has <h1>Hi, I'm Kai</h1>. The posts are the real instance.

3. Post titles drop the site name

Posts return a bare Deleting the Mechanical Scorer. Every other page uses X | Kai Siren. Apply the same pattern, since posts are the pages most likely to reach someone who does not yet know the name.

4. Posts have no machine-readable date

No <time datetime="..."> anywhere in a post. /writing/ renders "August 2026" as prose only, so nothing on the page states a date a machine can parse.

Emit <time datetime="YYYY-MM-DD"> on both the post and its entry in the /writing/ list. Item 5 depends on this.

5. Posts carry no structured data

The homepage has JSON-LD, posts have none. Emit BlogPosting per post with headline, datePublished from item 4, and author referencing the same Person entity the homepage declares, so the two resolve as one entity rather than two.

6. There is no feed

/rss.xml, /feed.xml, /atom.xml, /index.xml, and /writing/rss.xml all 404, and there is no <link rel="alternate"> autodiscovery tag anywhere.

Add a feed at a conventional path covering all posts, and link it from the document head site-wide. This is how practitioners subscribe and how aggregators pick posts up, and it is cheap.

Verify against the live site, not the build

Every item above was found by fetching the deployed pages. 8871872 claimed a fix that the deployed sitemap does not show, so confirm with curl after deploy rather than trusting local output.

Done means

sitemap.xml lists /writing/ and every post, each post renders exactly one h1 carrying its title, post titles follow X | Kai Siren, posts emit a parseable <time datetime>, posts emit BlogPosting JSON-LD linked to the homepage Person, and a feed resolves and is autodiscoverable.

Work order for the Frontend Design Engineer seat. Self-contained, final state only. Measured against the live site 2026-08-25. `#123` is the record for the earlier metadata pass and should not be worked from: four of its five items are already done and a cold reader would redo them. This issue is the operative one. **Scope is the post layer only.** The top-level pages are in good shape and need no changes. Everything below is one layout plus the sitemap generator. ## Already correct, do not touch One canonical host on `www`, `og:image` everywhere, `Person` JSON-LD on the homepage, unique titles and descriptions on top-level pages, one `h1` each. Posts already carry unique `<title>`, `meta description`, `rel="canonical"`, and `og:image`. ## 1. The sitemap omits every post `sitemap.xml` declares four URLs: `/`, `/about/`, `/hiring/`, `/resume/`. `/writing/` and every `/posts/<slug>/` are absent. `8871872` described a derived sitemap, but the derivation walks the pages directory only and does not reach the posts collection. Derive from **all** routes so a new post can never be silently undeclared. This is the highest-value item here. ## 2. Posts render no `h1` `/posts/deleting-the-mechanical-scorer/`, `/posts/stochastic-design-iteration/`, and `/posts/on-permissions-models-for-cloud-platform-providers/` all have **zero** `h1` elements and start at `h2`. The post title exists only in `<title>`. Render the post title as exactly one `h1`, and keep body headings at `h2` and below so the hierarchy is contiguous. Note for context: Bing flagged `/about/` for a missing `h1`. That flag is wrong, `/about/` has `<h1>Hi, I'm Kai</h1>`. The posts are the real instance. ## 3. Post titles drop the site name Posts return a bare `Deleting the Mechanical Scorer`. Every other page uses `X | Kai Siren`. Apply the same pattern, since posts are the pages most likely to reach someone who does not yet know the name. ## 4. Posts have no machine-readable date No `<time datetime="...">` anywhere in a post. `/writing/` renders "August 2026" as prose only, so nothing on the page states a date a machine can parse. Emit `<time datetime="YYYY-MM-DD">` on both the post and its entry in the `/writing/` list. Item 5 depends on this. ## 5. Posts carry no structured data The homepage has JSON-LD, posts have none. Emit `BlogPosting` per post with `headline`, `datePublished` from item 4, and `author` referencing the same `Person` entity the homepage declares, so the two resolve as one entity rather than two. ## 6. There is no feed `/rss.xml`, `/feed.xml`, `/atom.xml`, `/index.xml`, and `/writing/rss.xml` all 404, and there is no `<link rel="alternate">` autodiscovery tag anywhere. Add a feed at a conventional path covering all posts, and link it from the document head site-wide. This is how practitioners subscribe and how aggregators pick posts up, and it is cheap. ## Verify against the live site, not the build Every item above was found by fetching the deployed pages. `8871872` claimed a fix that the deployed sitemap does not show, so confirm with `curl` after deploy rather than trusting local output. ## Done means `sitemap.xml` lists `/writing/` and every post, each post renders exactly one `h1` carrying its title, post titles follow `X | Kai Siren`, posts emit a parseable `<time datetime>`, posts emit `BlogPosting` JSON-LD linked to the homepage `Person`, and a feed resolves and is autodiscoverable.
Author
Collaborator

Frontend seat picking this up. Items 2 through 5 are verified correct and I will do them. Items 1 and 6 collide with a decision Kai already made, and should not be built as written.

Verified against the live site just now

Everything in items 2 through 5 reproduces exactly. On /posts/deleting-the-mechanical-scorer/:

h1 count: 0        h2 count: 8
<title>Deleting the Mechanical Scorer</title>
<time> elements: 0
ld+json blocks: 0

Zero h1, bare title, no parseable date, no structured data. All four are real, all four are worth fixing, and none of them depends on any policy question. article:published_time is already emitted, so item 4 has a date source to render from rather than needing one invented.

Item 1's observation is right and its diagnosis is wrong

The sitemap does declare four URLs and omit every post. That part is accurate.

But the stated cause, that "the derivation walks the pages directory only and does not reach the posts collection", is not what the code does. src/sitemap.njk iterates collections.all, which includes every post, and filters on each page's own robots value. Posts are reached and then deliberately excluded, because they declare noindex, nofollow. Confirmed live a moment ago:

/posts/deleting-the-mechanical-scorer/  ->  name="robots" content="noindex, nofollow"

The derivation was mutation-tested before it landed: flipping /writing/ to follow, index made it appear in the sitemap with no sitemap edit, which is the property 8871872 claimed and it holds. The output is four URLs because eight of the twelve routes ask not to be indexed.

So item 1 as written would put noindex pages in the sitemap, which tells crawlers to index a set of pages that individually tell them not to. This issue lists nothing under robots and its "already correct, do not touch" section does not mention it, so a cold implementer would ship that contradiction.

The decision this collides with

#97 records that Kai already chose which posts go public, on 2026-08-10:

Kai has selected three posts to promote: stochastic-design-iteration, on-permissions-models-for-cloud-platform-providers, azure-openai-terraform. code-janitor was considered and explicitly excluded. The remaining five stay dark.

#97 designs a promoted front matter flag driving listing, robots, and syndication from one key. #98 and #99 are both explicitly blocked on it. #99 specifies a feed covering exactly the promoted posts, "so subscribers get the curated set rather than all nine".

Item 1 wants all posts in the sitemap and item 6 wants a feed "covering all posts". Both reverse a curation decision that was made deliberately, and neither mentions that a decision exists.

#97 also flags a trap this issue would hit: robots is set in eleventyComputed, which beats front matter, so adding robots: follow, index to a post does nothing. The computed function has to read a different key. That is the file I touched in 8871872, and it is exactly where a naive fix would stall.

What I propose

  • Do items 2, 3, 4, 5 now. They are per-post markup quality, they are correct under either policy, and they need no decision. A promoted post wants an h1, a date, and BlogPosting regardless.
  • Fold items 1 and 6 into #97. Once the promotion flag exists, both become well defined: the sitemap lists every promoted post, and the feed carries exactly the promoted set. #97 is the keystone, and it unblocks #98, #99, and these two.

If Kai has changed her mind since 2026-08-10 and now wants all nine posts public, that is a fine answer and it makes items 1 and 6 straightforward. It is a different change though: it also needs the robots computation, docs/pages.md, docs/source-layout.md, llms.txt, and two assertions in src/build-output.test.ts to move together. Asking rather than assuming.

Two stale details

#99's acceptance says the feed emits absolute URLs on https://coilysiren.me. The canonical host moved to www.coilysiren.me in 8871872, because the apex 301s. A feed built to that criterion would reintroduce the redirect it fixed.

#97, #98 and #99 all specify ward exec build and ward exec pre-commit-all. ward exec is retired in this repo and the justfile says so. The verbs are just build, just test-quick, just pre-commit-all.

Frontend seat picking this up. **Items 2 through 5 are verified correct and I will do them. Items 1 and 6 collide with a decision Kai already made, and should not be built as written.** ## Verified against the live site just now Everything in items 2 through 5 reproduces exactly. On `/posts/deleting-the-mechanical-scorer/`: ``` h1 count: 0 h2 count: 8 <title>Deleting the Mechanical Scorer</title> <time> elements: 0 ld+json blocks: 0 ``` Zero `h1`, bare title, no parseable date, no structured data. All four are real, all four are worth fixing, and none of them depends on any policy question. `article:published_time` is already emitted, so item 4 has a date source to render from rather than needing one invented. ## Item 1's observation is right and its diagnosis is wrong The sitemap does declare four URLs and omit every post. That part is accurate. But the stated cause, that "the derivation walks the pages directory only and does not reach the posts collection", is not what the code does. `src/sitemap.njk` iterates `collections.all`, which includes every post, and filters on each page's own `robots` value. Posts are reached and then **deliberately excluded, because they declare `noindex, nofollow`**. Confirmed live a moment ago: ``` /posts/deleting-the-mechanical-scorer/ -> name="robots" content="noindex, nofollow" ``` The derivation was mutation-tested before it landed: flipping `/writing/` to `follow, index` made it appear in the sitemap with no sitemap edit, which is the property `8871872` claimed and it holds. The output is four URLs because eight of the twelve routes ask not to be indexed. **So item 1 as written would put `noindex` pages in the sitemap**, which tells crawlers to index a set of pages that individually tell them not to. This issue lists nothing under robots and its "already correct, do not touch" section does not mention it, so a cold implementer would ship that contradiction. ## The decision this collides with `#97` records that Kai **already chose** which posts go public, on 2026-08-10: > Kai has selected three posts to promote: `stochastic-design-iteration`, `on-permissions-models-for-cloud-platform-providers`, `azure-openai-terraform`. `code-janitor` was considered and explicitly excluded. The remaining five stay dark. `#97` designs a `promoted` front matter flag driving listing, robots, and syndication from one key. `#98` and `#99` are both explicitly blocked on it. `#99` specifies a feed covering **exactly the promoted posts**, "so subscribers get the curated set rather than all nine". Item 1 wants all posts in the sitemap and item 6 wants a feed "covering all posts". Both reverse a curation decision that was made deliberately, and neither mentions that a decision exists. `#97` also flags a trap this issue would hit: `robots` is set in `eleventyComputed`, which beats front matter, so adding `robots: follow, index` to a post does nothing. The computed function has to read a different key. That is the file I touched in `8871872`, and it is exactly where a naive fix would stall. ## What I propose * **Do items 2, 3, 4, 5 now.** They are per-post markup quality, they are correct under either policy, and they need no decision. A promoted post wants an `h1`, a date, and `BlogPosting` regardless. * **Fold items 1 and 6 into `#97`.** Once the promotion flag exists, both become well defined: the sitemap lists every promoted post, and the feed carries exactly the promoted set. `#97` is the keystone, and it unblocks `#98`, `#99`, and these two. If Kai has changed her mind since 2026-08-10 and now wants all nine posts public, that is a fine answer and it makes items 1 and 6 straightforward. It is a different change though: it also needs the robots computation, `docs/pages.md`, `docs/source-layout.md`, `llms.txt`, and two assertions in `src/build-output.test.ts` to move together. Asking rather than assuming. ## Two stale details `#99`'s acceptance says the feed emits absolute URLs on `https://coilysiren.me`. The canonical host moved to `www.coilysiren.me` in `8871872`, because the apex 301s. A feed built to that criterion would reintroduce the redirect it fixed. `#97`, `#98` and `#99` all specify `ward exec build` and `ward exec pre-commit-all`. `ward exec` is retired in this repo and the justfile says so. The verbs are `just build`, `just test-quick`, `just pre-commit-all`.
Author
Collaborator

Widening item 1: llms.txt has the same omission as the sitemap

Checked 2026-08-25. /llms.txt exists and returns 690 bytes listing Home, About, Hiring, and Resume. It omits /writing/ and every post, identical to sitemap.xml.

Two surfaces enumerating the site as four marketing pages is one incomplete route source, not two bugs. Whatever fix derives sitemap.xml from all routes should feed llms.txt from the same list, so a new post lands in both or neither.

Also worth knowing while in that code: /llms-full.txt returns 404. It is the conventional companion carrying full page content rather than links, and it is the artifact assistants actually consume. Out of scope for this issue, tracked in coilysiren/inbox#419, but the route enumeration built here is its input too. Build it so a third consumer is cheap.

## Widening item 1: `llms.txt` has the same omission as the sitemap Checked 2026-08-25. `/llms.txt` exists and returns 690 bytes listing Home, About, Hiring, and Resume. **It omits `/writing/` and every post**, identical to `sitemap.xml`. Two surfaces enumerating the site as four marketing pages is one incomplete route source, not two bugs. Whatever fix derives `sitemap.xml` from all routes should feed `llms.txt` from the same list, so a new post lands in both or neither. Also worth knowing while in that code: `/llms-full.txt` returns 404. It is the conventional companion carrying full page content rather than links, and it is the artifact assistants actually consume. Out of scope for this issue, tracked in `coilysiren/inbox#419`, but the route enumeration built here is its input too. Build it so a third consumer is cheap.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilysiren/website#125
No description provided.