Skip to content

Site nav renders on 2 of 8 pages, though nav.jsonld says it is "identical on every page" #268

Description

@bdelanghe

Cold read of https://bounded.tools, 2026-09-03, against the live deploy (commit 3a4ac1d).

The drift

data/nav.jsonld describes itself as the canonical source, and states the invariant:

build.mjs renders the primary site nav (cross-page links, identical on every page) and the home-only on-page section list from this file, so the two never drift.

index.html:86 and contract/page.ts:36 repeat it. The build does not hold it. build.mjs:56:

for (const f of ["index.html", "404.html"]) {
  const p = join(dist, f);
  const html = await readFile(p, "utf8");
  await writeFile(p, html.replace("<!--SITE-NAV-->", siteHtml).replace("<!--DOC-TOC-->", tocHtml));
}

Two files. contracts.html, ledger.html and map.html are copied verbatim a few lines above with no nav placeholder to fill, and the generated pages (/conformance, /blog/*) carry their own hand-written headers.

What ships

Measured against the live site, four different navigations:

Pages Header
/, /404 Desk, Map, Contracts, Conformance, Ledger, Writing, GitHub
/conformance bounded.tools, Honesty, Proof, Writing, GitHub
/blog/, /blog/* bounded.tools, The bet, Writing, GitHub
/map, /contracts, /ledger no nav at all, only a ← bounded.tools back-link

Two consequences.

One destination, three names. /#status is "Status" on the home page, "Honesty" on /conformance, and "The bet" on the blog. A reader cannot learn the site's shape, because the shape changes under them.

Most of the site is a cul-de-sac. The full internal link graph of the built pages:

  • /contracts links only to /.
  • /ledger links only to / and /ledger.jsonl.
  • /map links to /, /contracts, /ledger.
  • /conformance is linked from /, /404, and one blog post. Nothing else reaches it.

So a visitor who lands on /contracts from a search result has one way onward, and the conformance report has no path in from three of the five internal pages.

Why this one matters more than its size

The repo's stated bet is that generated surfaces cannot drift from their source. Here a hand-maintained header drifted from a canonical JSON-LD file that exists specifically to prevent it, and the assertion that it cannot drift is shipped in the served HTML. The gate that would catch it is the one that was never written.

Suggested shape

  1. Give every page in dist/ the <!--SITE-NAV--> placeholder and widen the build.mjs loop to every HTML file it emits, rather than a hardcoded pair. The generated pages (gen-conformance.mjs, gen-blog.mjs) should emit the placeholder too and drop their hand-written headers.
  2. Add a check that fails the build when any page in dist/ lacks a <nav aria-label="Main"> whose links equal nav.jsonld's site[]. That is the forcing function the comment currently asserts without one.
  3. Separately: the home page's "Writing." block links one post (trust-lives-outside-the-page) and not /blog/, so five of six posts are reachable only through the nav item that most pages do not have.

Filed from a cold read, unclaimed and unassigned.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions