Files
blog/GOAL.md

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 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>/. — re-ran npm 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-Prototypefirst-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 to public/assets/posts/What-do-games-need/cover.jpg; 1 dead external link (Caltech101 dataset, old vision.caltech.edu 404) repointed to its current home at data.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 in 2016-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 in 2026-08-06-Backrooms.md. Manually verified all 12 itch.io "403" refs plus kryptview.com and www.cali-rse.com are 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, commented KNOWN_LIVE_BUT_BLOCKED allowlist in scripts/audit/link-image-audit.mjs so the audit stops reporting the same false positives. npm run audit:links:full now comes back clean (0 broken internal, 0 broken external, 13 allowlisted). npm run build passes; 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 /tags a 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 — TypescriptTypeScript (2022-08-15-Matters-internship.md), a true duplicate pair that toSlug() already merged for routing while the tag cloud still listed both, and HtmlHTML (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"), which src/content/config.ts's zod schema validates every post's tags/techs against — an unknown value now fails npm run build (verified with a temporary negative test: adding a bogus tag reproduced InvalidContentEntryFrontmatterError, 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.astro and [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 a tags entry to the site nav. npm run build passes clean (108 pages), the postbuild search-index step still mirrors into dist/, and npm run audit:links:full comes back clean (0 broken, 13 allowlisted — unaffected by this change). /tags, /tags/gamedev and a post page were screenshotted with the .dark class both present and stripped — the new tag-index markup uses only design tokens so it's identical in both states (correct: no Tailwind dark: utilities in it), confirmed against the AI-disclosure callout on the ai-agent post 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 sane cover; validate it in the zod schema so a missing one fails the build. — all 27 existing posts already had both fields populated; made description/cover required (.min(1)) in the blog collection's zod schema (src/content/config.ts), so any future post missing either now fails npm run build with a clear InvalidContentEntryFrontmatterError (verified with a temporary negative test — removing cover from one post reproduced the failure, then restored). npm run build passes 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.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.