Spec 0001: Core Publishing Site

Overview

Build a Git-native, Markdown-based publishing site for The Long Run using Astro and Cloudflare Pages.

Goals

  1. Establish Git + Markdown as the source of truth for all content
  2. Deploy a minimal, editorial-focused static site to thelongrun.work
  3. Support AI-assisted writing without AI-controlled publishing
  4. Create a system that works unchanged for 5+ years

Non-Goals

  • Auto-publishing to third-party platforms (Medium, LinkedIn)
  • Self-hosted databases or persistent backend services
  • AI publishing without human review
  • Comments or analytics (can be added later)

Requirements

R1: Repository Structure

Create the following structure:

thelongrun-site/
├── content/
│   ├── essays/           # Long-form articles
│   ├── notes/            # Short posts (future)
│   └── pages/
│       └── about.md
├── assets/
│   ├── avatar.svg
│   └── images/
├── src/                  # Astro source
│   ├── layouts/
│   ├── components/
│   └── styles/
├── public/
│   └── favicon.png
├── scripts/              # Build/prepare scripts
├── astro.config.mjs
├── package.json
├── tsconfig.json
└── README.md

Rationale: content/ is editorial (human-readable, AI-editable). src/ is presentation logic. No mixing of concerns.

R2: Content Format

All essays use YAML frontmatter:

---
id: "001"
title: "When does the desktop—or IDE—stop being the centre of development?"
subtitle: "Exploring long-running agents, async work, and why the IDE still matters."
date: 2025-01-15
status: draft | review | published
canonical: https://thelongrun.work/essays/desktop-ide-centre
tags:
  - software
  - work
  - ai
---
 
<markdown body>

Rules:

  • canonical always points to the Cloudflare Pages URL
  • Only status: published content is rendered publicly
  • Drafts must never appear on the public site
  • id is a sequential string identifier for cross-referencing

R3: Static Site Behavior

Pages:

  • Home (/): List of published essays, reverse chronological
  • Essay pages (/essays/{slug}): Title, subtitle, body, date, canonical meta
  • About (/about): Static page from content/pages/about.md

SEO Requirements:

  • <link rel="canonical"> set on all pages
  • OpenGraph metadata derived from frontmatter (og:title, og:description, og:url)
  • RSS feed (/rss.xml) generated from published essays

Design:

  • Minimal typography, editorial tone
  • No comments, no analytics
  • Mobile responsive

R4: Build and Deployment

Technology:

  • Astro (latest stable)
  • Node.js LTS

Build:

  • npm run build produces dist/
  • npm run dev for local development
  • Only status: published essays appear in production build

Cloudflare Pages:

  • Build command: npm run build
  • Output directory: dist/
  • Deploy on every push to main
  • Preview builds for PRs
  • Domain: thelongrun.work with HTTPS enforced

R5: Editorial Workflow

draft → review → published

Rules:

  • Only humans change status
  • Publishing requires: clean local build + manual review
  • Git commit history is the editorial record

AI Guardrails:

  • AI may: draft outlines, rewrite paragraphs, improve clarity, suggest titles
  • AI must not: change status to published, edit build config, commit to main without review

Technical Implementation

Astro Configuration

// astro.config.mjs
import { defineConfig } from 'astro/config';
 
export default defineConfig({
  site: 'https://thelongrun.work',
  output: 'static',
});

Content Collections

Use Astro’s content collections for type-safe content:

// src/content/config.ts
import { z, defineCollection } from 'astro:content';
 
const essays = defineCollection({
  type: 'content',
  schema: z.object({
    id: z.string(),
    title: z.string(),
    subtitle: z.string().optional(),
    date: z.date(),
    status: z.enum(['draft', 'review', 'published']),
    canonical: z.string().url(),
    tags: z.array(z.string()).default([]),
  }),
});
 
export const collections = { essays };

Draft Filtering

Filter content at build time, not runtime:

// In page generation
const publishedEssays = await getCollection('essays', ({ data }) => {
  return data.status === 'published';
});

Acceptance Criteria

  1. Site builds successfully - npm run build completes without errors
  2. Home page works - Lists published essays in reverse chronological order
  3. Essay pages work - Individual essay URLs render correctly
  4. Drafts are hidden - Essays with status: draft or status: review do not appear
  5. Canonical links correct - <link rel="canonical"> matches frontmatter
  6. RSS feed works - /rss.xml contains published essays
  7. Cloudflare deploys - Push to main triggers successful deployment
  8. Preview builds work - PRs generate preview URLs

Out of Scope

  • Platform publishing scripts (see Spec 0002)
  • n8n automation
  • AI chat integration
  • Analytics or comments

Design Philosophy

Build this as if it must still work, unchanged, in five years.

  • Prefer clarity over cleverness
  • Prefer boring, proven defaults
  • Prefer human review over automation
  • Treat writing as long-lived artifacts