Files
com.gregkilmer.www/docs/site.md
T
Claude 7025e95f89 Site app foundation on the content service
services/site is an Astro app rendered on request by the Node adapter. It
keeps every published note in memory from the content service, refreshes
in the background, and keeps serving the last good copy if the service is
down. SiteIndex answers lookups, addresses, links both ways with roll-ups,
the status log and safe Markdown. Vault images pass through /media.
docs/site.md has the URL map and page rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-11 21:15:39 +00:00

3.7 KiB

The site

services/site (@gk/site) is the public GregKilmer.com. It is an Astro app rendered on request by the Node adapter. It reads only the content service, with a reader token, and only published notes.

How a page gets its data

---
import PageShell from "@gk/ui/components/PageShell.astro";
import { site } from "../lib/page.ts";
const s = await site();            // the SiteIndex: every published note in memory
const games = s.all("board-game"); // typed: games[0].data.bggId is a number
---

src/lib/content.ts loads every published note from the content service and refreshes in the background every 30 seconds (CONTENT_REFRESH_SECONDS). If the service is down, the last good copy keeps serving.

src/lib/site.ts (SiteIndex) answers everything a page asks:

Method Gives
all(type), one(type), bySlug(type, slug), byName(name or [[link]]) Notes.
href(note) The address that shows a note (see the URL map below).
link("[[Name]]") { title, href, note }; href is null when the note is not published, so render plain text.
backlinks(note, { type }) Notes linking to it, newest first. Links to a make count for its craft, a deck for its game, a campaign for its system.
linksFrom(note) Notes it links to.
posts({ topic }), caseStudies() Blog posts and case studies, newest first.
statuses(), statusFor(note) The status log (newest per subject, pinned first) and a note's current status.
kind(label) A status kind's brand icon and page.
pageText(path) The Page text note for an address: lede, body, quick facts.
settings() Site settings: name, tagline, availability, socials.
render(markdown) Safe HTML with wikilinks resolved.
redirect(type, slug) Where an old slug now lives; answer it with a 301.

mediaSrc(value) turns an image field into a /media/<file> address, which passes vault images through from the content service. formatDate("2026-10-11") gives "Oct 11, 2026".

URL map

Address Shows
/ Landing
/about About me
/resume, /resume.pdf Resume and its PDF
/work, /work/<slug> Case studies and each study
/inquiries, /inquiries/sent Inquiry form and its confirmation
/library, /library/shelf The Library and The Shelf; items are anchors #<slug>
/workshop, /workshop/<craft> Workshop and each craft; makes are anchors on their craft
/tabletop Systems, campaigns, card games and decks as anchors
/now Every status; each is an anchor
/blog, /blog/topic/<topic>, /blog/<slug>, /rss.xml Blog, topics, posts, feed
/store, /store/<line>, /store/order Store, product lines, order form
anything else 404, Off the star chart

Rules for pages

  • Build from the components in @gk/ui (see components.md) and the gk-* classes. Every page uses PageShell with its family (edge seal) and current (nav entry); its hero goes in the hero slot.
  • All dark. Panels are Midnight with a gold frame, not Starlight.
  • Never invent facts or numbers. Where a note has no value, leave the element out, or show [PLACEHOLDER] where the design needs text Greg has not written yet.
  • No phone numbers. Email addresses stay as they are in the notes.
  • Lists of posts are fixed-height rows, not stretching cards.
  • Small client scripts (filters, tabs) are fine; the page must read correctly without them.

Running it locally

# the content service, against a copy of the Site folder
SITE_DIR=… DATA_DIR=… CONTENT_TOKENS="reader:<24+ chars>" npm run content
# the site
CONTENT_URL=http://localhost:4310 CONTENT_TOKEN=<reader token> npm run site   # http://localhost:4330