15 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 ofnpm 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 intopostbuild(internal checks only — fast, no network); full run with external HTTP checks vianpm 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>/. — re-rannpm run audit:links:full, which found 4 broken internal links and 18 dead external refs (matching the prior report). Fixed: 4 dead pre-Astro internal paths repointed to their real/blog/<slug>routes (temple-test.mdx's/GrenGame/First-Prototype→first-prototype;tictactocalypse.md's three/Projects/Tictactocalypse/...links →rulesheet/mda/finalreflection); 1 dead external cover image (perforce.com, 404) vendored from a Wayback Machine snapshot topublic/assets/posts/What-do-games-need/cover.jpg; 1 dead external link (Caltech101 dataset, oldvision.caltech.edu404) repointed to its current home atdata.caltech.edu; 1 dead link (isart.com's old summer-camp page, 404, no confirmed live replacement on their own site) unlinked to plain text in2016-07-10-Primitive-Tower-defense.mdx; 1 dead link (git.gabvdl.xyz/gabrielvidal/backrooms— the repo exists but is private, unlike every other project repo linked from a post; making it public is a visibility/security call left for a human) unlinked to plain text in2026-08-06-Backrooms.md. Manually verified all 12 itch.io "403" refs pluskryptview.comandwww.cali-rse.comare genuinely live (Playwright + Jina Reader render real content, or a web search confirms an operating company) — these are bot-walls, not dead pages — and added them to an explicit, commentedKNOWN_LIVE_BUT_BLOCKEDallowlist inscripts/audit/link-image-audit.mjsso the audit stops reporting the same false positives.npm run audit:links:fullnow comes back clean (0 broken internal, 0 broken external, 13 allowlisted).npm run buildpasses; both fixed posts and the tictactocalypse project page were screenshotted in light and dark mode — images and the repointed links render correctly. - Tag taxonomy pass: consolidate the tag vocabulary (they accreted over
eleven years), give each tag a description, and make
/tagsa real index rather than a word cloud. — audited all 24 posts' frontmatter: 12 topic tags and 24 tech labels (36 total), of which 2 were casing drift —Typescript→TypeScript(2022-08-15-Matters-internship.md), a true duplicate pair thattoSlug()already merged for routing while the tag cloud still listed both, andHtml→HTML(2021-10-05-Digital-Resume.md), a lone lowercase outlier normalised to match the other acronym tags. No other near-duplicates or noise tags found; the vocabulary was already disciplined, just case-drifted. Descriptions for the resulting 35 canonical tags (12 topics + 23 techs) now live in one place,src/data/tags.ts(TagDefinition[],kind: "topic" | "tech"), whichsrc/content/config.ts's zod schema validates every post'stags/techsagainst — an unknown value now failsnpm run build(verified with a temporary negative test: adding a bogus tag reproducedInvalidContentEntryFrontmatterError, then reverted). Rebuilt/tags(src/pages/tags/index.astro, new route) as a real index: two grouped sections (Topics / Technologies), each tag card showing its description and post count, sorted by usage then name — not a word cloud. The per-tag pages (src/pages/tags/[tag]/index.astroand[page].astro) now show the canonical label and description instead of the raw slug;Archive.astro's tag list got the same "Sort by tag" heading treatment as its tech list already had, plus a link to/tags; added atagsentry to the site nav.npm run buildpasses clean (108 pages), thepostbuildsearch-index step still mirrors intodist/, andnpm run audit:links:fullcomes back clean (0 broken, 13 allowlisted — unaffected by this change)./tags,/tags/gamedevand a post page were screenshotted with the.darkclass both present and stripped — the new tag-index markup uses only design tokens so it's identical in both states (correct: no Tailwinddark:utilities in it), confirmed against the AI-disclosure callout on theai-agentpost which does visibly change, ruling out a broken toggle. - 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 sanecover; validate it in the zod schema so a missing one fails the build. — all 27 existing posts already had both fields populated; madedescription/coverrequired (.min(1)) in the blog collection's zod schema (src/content/config.ts), so any future post missing either now failsnpm run buildwith a clearInvalidContentEntryFrontmatterError(verified with a temporary negative test — removingcoverfrom one post reproduced the failure, then restored).npm run buildpasses clean on the full archive; a post page was screenshotted in light and dark mode to confirm rendering is unaffected (schema-only change). - 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.pngstays 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)
/transparencypage: 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_byfrontmatter field validated by the schema, feeding both that page and a small badge on the post header.- Build-time check: a post whose
generated_byis set but whose body lacks the AI-disclosure callout fails the build. The rule currently lives inCLAUDE.mdand 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 route —
src/drafts/renders inastro devbehind a flag and is excluded from the build, so a half-written post can be looked at without publishing it. - Series / collection support: a
seriesfrontmatter 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
postbuildstep whosedist/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.
originis the self-hosted Gitea (canonical);githubis 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.cssis structure and reads tokens only;blueprint.cssfills them. Tokens live onhtml:root(a barehtml {}loses to:rootregardless of order), and design rules keep thehtmlprefix. - 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/.mdxon purpose, or classes used only inside posts vanish. - The search index is a
postbuildstep that writespublic/and mirrors intodist/; that mirror is load-bearing.npm run deployrefuses without it. - Verify before deploying:
npm run build, then actually look at the rendered page (screenshot it) in both light and dark mode.