Plan 0001: Core Publishing Site

Spec: codev/specs/0001-core-publishing-site.md

Implementation Strategy

Build incrementally with working deployments at each phase. Each phase produces a testable artifact.

Phase 1: Project Scaffolding

Goal: Working Astro project with correct structure.

Tasks

  1. Initialize Astro project in repository root:

    npm create astro@latest . -- --template minimal --typescript strict
  2. Create directory structure:

    content/
    ├── essays/
    ├── notes/
    └── pages/
        └── about.md
    assets/
    └── images/
    
  3. Configure astro.config.mjs:

    import { defineConfig } from 'astro/config';
     
    export default defineConfig({
      site: 'https://thelongrun.work',
      output: 'static',
    });
  4. Add sample content file content/essays/001-sample.md with proper frontmatter (status: draft)

  5. Verify: npm run dev starts without errors

Deliverables

  • Astro project initialized
  • Directory structure created
  • Sample essay with frontmatter
  • Dev server runs

Phase 2: Content Collections

Goal: Type-safe content model with draft filtering.

Tasks

  1. Create 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([]),
      }),
    });
     
    const pages = defineCollection({
      type: 'content',
      schema: z.object({
        title: z.string(),
      }),
    });
     
    export const collections = { essays, pages };
  2. Move content to Astro’s expected location:

    • content/essays/src/content/essays/
    • content/pages/src/content/pages/
  3. Create helper for filtering published content:

    // src/lib/content.ts
    import { getCollection } from 'astro:content';
     
    export async function getPublishedEssays() {
      const essays = await getCollection('essays', ({ data }) => {
        return data.status === 'published';
      });
      return essays.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
    }
  4. Add a second essay with status: published for testing

  5. Verify: Content loads with type checking, draft filtering works

Deliverables

  • Content collections configured
  • Schema validation works
  • Published/draft filtering works
  • TypeScript types inferred correctly

Phase 3: Page Templates

Goal: Functional pages for home, essays, and about.

Tasks

  1. Create base layout src/layouts/Base.astro:

    • HTML boilerplate
    • Slot for content
    • Minimal CSS (system fonts, readable line length)
  2. Create home page src/pages/index.astro:

    • List published essays
    • Show title, date, subtitle
    • Link to essay page
  3. Create essay page src/pages/essays/[...slug].astro:

    • Dynamic route for all essays
    • Render title, subtitle, date, body
    • Only generate pages for published essays
  4. Create about page src/pages/about.astro:

    • Render from src/content/pages/about.md
  5. Add minimal styles src/styles/global.css:

    • Typography: system font stack
    • Max-width container
    • Responsive padding
  6. Verify: All pages render correctly, navigation works

Deliverables

  • Base layout with minimal styling
  • Home page lists published essays
  • Essay pages render correctly
  • About page works
  • Drafts do NOT generate pages

Phase 4: SEO and RSS

Goal: Proper meta tags and RSS feed.

Tasks

  1. Add SEO component src/components/SEO.astro:

    ---
    interface Props {
      title: string;
      description?: string;
      canonical: string;
      type?: 'website' | 'article';
    }
    const { title, description, canonical, type = 'website' } = Astro.props;
    ---
    <link rel="canonical" href={canonical} />
    <meta property="og:title" content={title} />
    <meta property="og:url" content={canonical} />
    <meta property="og:type" content={type} />
    {description && <meta property="og:description" content={description} />}
  2. Integrate SEO into layouts/pages

  3. Create RSS feed src/pages/rss.xml.ts:

    import rss from '@astrojs/rss';
    import { getPublishedEssays } from '../lib/content';
     
    export async function GET(context) {
      const essays = await getPublishedEssays();
      return rss({
        title: 'The Long Run',
        description: 'Essays on software, work, and AI',
        site: context.site,
        items: essays.map((essay) => ({
          title: essay.data.title,
          pubDate: essay.data.date,
          link: `/essays/${essay.slug}/`,
        })),
      });
    }
  4. Install RSS package: npm install @astrojs/rss

  5. Verify: View source shows canonical tags, RSS validates

Deliverables

  • Canonical links on all pages
  • OpenGraph meta tags
  • RSS feed at /rss.xml
  • Feed validates (use W3C validator)

Phase 5: Cloudflare Deployment

Goal: Automated deployment on push to main.

Tasks

  1. Create wrangler.toml (if needed) or use Cloudflare dashboard

  2. Configure Cloudflare Pages:

    • Connect GitHub repository
    • Build command: npm run build
    • Output directory: dist
    • Node version: 20 (LTS)
  3. Set up custom domain:

    • Add thelongrun.work to Cloudflare
    • Configure DNS to point to Pages
    • Enable HTTPS
  4. Test deployment:

    • Push to main, verify build succeeds
    • Create PR, verify preview build
  5. Add deployment badge to README

Deliverables

  • Cloudflare Pages project created
  • Auto-deploy on push to main
  • Preview builds for PRs
  • Custom domain configured
  • HTTPS working

Testing Strategy

Manual Testing Checklist

Before each phase completion:

  • npm run build succeeds with no errors
  • npm run dev works for local development
  • No TypeScript errors
  • Draft essays do not appear anywhere public

Final Acceptance Testing

  • Visit https://thelongrun.work - home page loads
  • Published essays appear, drafts do not
  • Individual essay pages work
  • Canonical links match frontmatter
  • RSS feed at /rss.xml is valid
  • About page renders
  • Mobile responsive

File Checklist

Files to create:

astro.config.mjs
package.json
tsconfig.json
src/content/config.ts
src/lib/content.ts
src/layouts/Base.astro
src/components/SEO.astro
src/pages/index.astro
src/pages/about.astro
src/pages/essays/[...slug].astro
src/pages/rss.xml.ts
src/styles/global.css
src/content/essays/001-sample.md
src/content/pages/about.md
public/favicon.png (placeholder)
README.md

Notes for Builder

  • Use Astro 5.x (latest stable)
  • Keep dependencies minimal - only add what’s necessary
  • No client-side JavaScript unless absolutely required
  • Test draft filtering at every phase
  • The spec says “5 year horizon” - avoid trendy patterns that may not last