253 lines
15 KiB
Markdown
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.
|