Files
blog/GOAL.md

253 lines
15 KiB
Markdown

# 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
<!-- Claims by goal-keeper agents. One bullet per in-flight item; the agent
removes its own line in the same commit that ticks the checkbox. Leave
the section empty (this comment only) when nothing is in flight. -->
## 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
- [x] **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.
- [x] **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-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 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.
- [x] **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 —
`Typescript``TypeScript` (`2022-08-15-Matters-internship.md`), a true
duplicate pair that `toSlug()` already merged for routing while the tag
cloud still listed both, and `Html``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"`), 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.
- [x] **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 route**`src/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.