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
This commit is contained in:
Claude
2026-10-11 21:15:39 +00:00
parent 1023882e19
commit 7025e95f89
15 changed files with 674 additions and 2 deletions
@@ -0,0 +1,39 @@
---
/**
* The hero inside the site header: a gold eyebrow, the page title in Cinzel
* and a lede. Extra content (buttons, chips, a filter) goes in the slot.
*/
interface Props {
eyebrow?: string;
title: string;
lede?: string;
}
const { eyebrow, title, lede } = Astro.props;
---
{eyebrow && <div class="eyebrow">{eyebrow}</div>}
<h1 class="title">{title}</h1>
{lede && <p class="lede">{lede}</p>}
<slot />
<style>
.eyebrow {
font-size: 11px;
letter-spacing: 0.3em;
font-weight: 600;
color: var(--gk-gold-bright);
text-transform: uppercase;
}
.title {
margin: 0;
font-size: clamp(34px, 6vw, 48px);
letter-spacing: 0.08em;
line-height: 1.05;
}
.lede {
margin: 0;
font-size: 17px;
color: var(--gk-soft);
max-width: 62ch;
line-height: 1.6;
}
</style>
@@ -0,0 +1,34 @@
---
/**
* A status as a live pill: "Reading" with a lit dot, linking to the page the
* status is about when there is one.
*/
import StatusPill from "@gk/ui/components/StatusPill.astro";
import type { Note } from "../lib/content.ts";
import type { SiteIndex } from "../lib/site.ts";
interface Props {
status: Note<"status">;
site: SiteIndex;
/** Show the subject after the kind, as the Now strip does. */
withSubject?: boolean;
}
const { status, site, withSubject } = Astro.props;
const subject = status.data.subject ? site.link(status.data.subject) : null;
const label = withSubject && subject ? `${status.data.kind} · ${subject.title}` : status.data.kind;
---
{
subject?.href && withSubject ? (
<a href={subject.href} class="status-link">
<StatusPill>{label}</StatusPill>
</a>
) : (
<StatusPill>{label}</StatusPill>
)
}
<style>
.status-link {
text-decoration: none;
}
</style>