Files
blog/GOAL.md
Gabriel Vidal d40c354c4a feat(audit): add a link & image audit script, warn-only in the build
Walks every post's <img>/<a> (markdown + raw HTML, .md/.mdx, plus the
frontmatter cover field) across both content collections, flags missing
local assets and internal links that don't match any real route, and can
optionally probe external refs for dead hosts (HEAD-then-GET, cached,
concurrency-capped, off by default).

Wired into `npm run build`'s postbuild step in warn mode — it can never
fail the build, only crash-guards + logs a warning. External checks are a
separate on-demand npm run audit:links:full since they touch the network
and would make every build network-dependent otherwise. Report artefacts
land in .ai/link-audit/ (gitignored).

First real run: 0 missing local assets, 4 broken internal links (dead
pre-Astro /Projects/... style paths from the old site structure), 18 dead
external refs with --external (6 confirmed, 12 low-confidence itch.io-style
403s that are more likely bot-blocking than actually dead — flagged as such
in the report rather than reported at face value).

Ticks the "Link & image audit" wishlist item in GOAL.md; "Fix what the
audit finds" is next and deliberately untouched here.
2026-08-10 01:58:30 +02:00

10 KiB

GOAL — where blog is going

North star

A blog worth subscribing to: honest, measured write-ups of what the lab actually did — every number taken from a real run, every AI-written word labelled as such — on a site I host, that loads in a second, and that will still resolve in ten years.

The archive spans 2015 to today: student game-design essays, internships, prototypes, and now the homelab. The bet is that the lab is the material — a Strix Halo box doing image→3D, agents running from a phone, a Backrooms map generator — and that writing it down carefully is worth more than posting often.

Modest by design: the goal is not an audience number. It is an archive that is presentable end to end, a publishing loop that doesn't stall, and a transparency record nobody else in this genre bothers to keep.

Target

  • Me — writing is how the lab work gets thought through and remembered. The post is the artefact that outlives the project.
  • The person who hit this from a search — someone debugging ROCm on gfx1151, or wondering whether agents-from-a-phone is real. They want measurements and code, not a listicle. They should be able to read it with JS off.
  • People who follow the lab — via RSS, on their own reader, with no newsletter signup and no tracking beyond self-hosted Matomo.

What exists today (v1.0)

Astro 2 (astro-ink lineage) with MDX posts, client-side Lunr search, tags, RSS and a sitemap; static build → zipgo on raspy2 at blog.dev.gabvdl.xyz. One design, the blueprint — a cyanotype engineering drawing, graph-paper ground, FIG. numbering, a drawing title block for a footer — with a load-bearing split between base.css (structure, tokens only) and blueprint.css (identity). Animation islands from @gabvdl/ui hydrate without flashing and leave complete HTML for crawlers; ArticleExtras gives every post a full-screen image viewer, a "play the figures as a story" control and copy buttons for free. 24 posts, three of them recent AI-written lab articles carrying the mandatory AI-disclosure callout. Analytics is self-hosted Matomo. No CI: nothing publishes on push, only npm run deploy.

Being worked on

Horizons

Short term — v1.1: the archive is presentable (now)

Eleven years of posts moved through three themes and two hosts. Before writing anything new, make what exists hold up: no broken images or dead links, tags that mean something, every post legible in the blueprint look in both colour schemes, and correct metadata (dates, descriptions, canonical URLs, per-post social cards).

Middle term — v1.2: a publishing loop and a transparency record

Make the site say out loud what it is: a public page listing which posts were written by a model and which by me, wired to the lab's AI-transparency convention. Alongside it, the mechanics that keep publishing from stalling — a draft queue that renders locally, series/collection pages, prev-next navigation, reading time, and a full-content RSS feed that reads well in a reader.

Long term — v2 / someday

A site people subscribe to: a steady rhythm of lab write-ups, a few of them the canonical English-language reference for something niche (ROCm on Strix Halo, agent harness economics), an archive page that makes eleven years navigable, and a look that is unmistakably this site and nobody else's. Still static, still self-hosted, still free of trackers.

Wishlist

Ordered roughly by value. Items marked [human] need a taste call or writing that is mine to do — the goal-keeper must skip those.

The archive holds up

  • Link & image audit: a script that walks every post's <img> and <a>, checks local assets exist and reports external 404s, run as part of npm run build (warn) with a report artefact. Several 2015-2021 posts have already had broken images fixed by hand. — scripts/audit/link-image-audit.mjs, wired into postbuild (internal checks only — fast, no network); full run with external HTTP checks via npm run audit:links:full. Report at .ai/link-audit/report.{json,md}. First real run found 4 broken internal links (dead pre-Astro /Projects/...-style paths) and, with --external, 18 dead external refs (6 confirmed 404/unreachable, 12 low-confidence 403s — mostly itch.io, which blocks scripted requests from this host regardless of whether the page is live). 0 missing local assets. Fixing what it found is the next wishlist item, deliberately left undone here.
  • Fix what the audit finds — one pass over the old posts, replacing dead external images with local copies under public/assets/posts/<Post>/.
  • Tag taxonomy pass: consolidate the tag vocabulary (they accreted over eleven years), give each tag a description, and make /tags a real index rather than a word cloud.
  • Dark-mode audit of the old posts — tables, inline code, callouts and raw-HTML blocks in pre-2023 posts, which predate the token system.
  • Frontmatter completeness: every post gets a description (used by search, RSS and og:description) and a sane cover; validate it in the zod schema so a missing one fails the build.
  • Per-post og:image: generate a blueprint-styled social card per post (title + date + tags on the graph-paper ground) at build time. The site's curated public/og-image.png stays as the home-page default — this is per-post, not a screenshot.
  • Archive page: all posts by year, with counts — eleven years is too much for one scroll.

Transparency (the disclosure record)

  • /transparency page: every post listed with who wrote it — me, or a named model working from my repos and runs — derived from frontmatter, not hand-maintained. Explain the rule in one paragraph: any generated post carries the callout, and no number in one is estimated.
  • author / generated_by frontmatter field validated by the schema, feeding both that page and a small badge on the post header.
  • Build-time check: a post whose generated_by is set but whose body lacks the AI-disclosure callout fails the build. The rule currently lives in CLAUDE.md and depends on the agent remembering it — make it mechanical.
  • Adopt the lab's ai-transparency widget convention if it fits without bloating the page (~/projects/ai-transparency-widget).

The publishing loop

  • Prev/next post navigation and related-by-tag links at the end of a post.
  • Reading time + word count in the post header.
  • Full-content RSS (currently likely summary-only) with correct enclosures for cover images, validated against a feed validator.
  • Draft preview routesrc/drafts/ renders in astro dev behind a flag and is excluded from the build, so a half-written post can be looked at without publishing it.
  • Series / collection support: a series frontmatter field with an index page (the three 2026 lab posts are already a series in everything but name).
  • Search quality: the Lunr index should cover descriptions and tags, and the results UI should show a snippet — the index is already a postbuild step whose dist/ mirror is load-bearing, don't break that.
  • Blueprint-styled 404 page and a proper /about.

Craft & performance

  • Lighthouse pass on a post page — image dimensions, loading="lazy", font loading, no layout shift from the animation islands.
  • Image pipeline: convert the large post screenshots to WebP/AVIF with width variants at build time; the TRELLIS post alone is image-heavy.
  • Print stylesheet for a long technical post.
  • [human] Decide whether the blueprint identity extends to the RSS/og surfaces or stays on-site only.

Writing (mine)

  • [human] A post on the homelab itself — Traefik, Authelia, the generated root compose, and why it is worth running.
  • [human] A post on what a year of agent-run projects actually cost, using the real numbers the lab already records.

Non-goals (for now)

  • A newsletter, signups, or an email list. RSS is the subscription.
  • Any third-party analytics, ads, comments platform, or embedded tracker. Self-hosted Matomo, cookie-less, is the ceiling.
  • CI that publishes on push. Deploying is a deliberate act; the deploy script's guard (refuse without a fresh search index) is part of that.
  • A CMS or a headless backend. A post is one markdown file in the repo.
  • Chasing SEO or posting frequency for its own sake. Fewer, measured posts.
  • A second design. The switcher was deliberately removed; blueprint is the look.

Guard rails (for the goal-keeper)

  • One wishlist item per run, finished end-to-end, then commit and push. origin is the self-hosted Gitea (canonical); github is a dormant mirror — pushing there is optional and nothing depends on it.
  • Never publish a generated post without the AI-disclosure callout, verbatim as specified in CLAUDE.md, as the first thing in the body. This is not negotiable and not something to ask about.
  • Never invent a number. Benchmarks, timings, costs and code in a post come from a real run or a real file — measure or read it, never estimate.
  • Don't write a new post on a scheduled run. Editorial voice is Gabriel's; the goal-keeper improves the site, fixes the archive, and prepares tooling. A post is written only when asked for by name.
  • Respect the CSS split: base.css is structure and reads tokens only; blueprint.css fills them. Tokens live on html:root (a bare html {} loses to :root regardless of order), and design rules keep the html prefix.
  • Import islands by one specifier (src/components/islands/X.tsx, with the extension) — mixing relative and aliased imports breaks the Astro client build at generate time.
  • Don't narrow the Tailwind content glob — it scans .md/.mdx on purpose, or classes used only inside posts vanish.
  • The search index is a postbuild step that writes public/ and mirrors into dist/; that mirror is load-bearing. npm run deploy refuses without it.
  • Verify before deploying: npm run build, then actually look at the rendered page (screenshot it) in both light and dark mode.