Plan: Install Quarto and Configure Blog MVP
Context
Setting up a Quarto-powered blog on GitHub Pages with F# Jupyter notebook support. This is the Phase 1 MVP from the product roadmap. The key architectural insight is the “freeze” strategy: author notebooks locally with the .net-fsharp kernel, execute all cells, commit the _freeze/ directory, and let CI render HTML without ever needing the F# kernel.
The repo is a fresh start — no existing site config, workflows, or blog files.
Task 1: Save Spec Documentation
Create agent-os/specs/2026-03-29-install-quarto-blog/ with:
plan.md— This full planshape.md— Shaping notes (scope, decisions, context)references.md— Quarto docs references
Task 2: Create Quarto Blog Scaffolding
Create these files in the repo root:
_quarto.yml
project:
type: website
website:
title: "Kurt Mueller"
site-url: https://everybodykurts.github.io
description: "A blog about F# and functional programming"
navbar:
right:
- about.qmd
- icon: github
href: https://github.com/everybodykurts
- icon: rss
href: index.xml
format:
html:
theme: cosmo
css: styles.css
execute:
freeze: autoindex.qmd
---
title: "Kurt Mueller"
listing:
contents: posts
sort: "date desc"
type: default
categories: true
feed: true
---about.qmd
---
title: "About"
---
Welcome to my blog. I write about F# and functional programming.styles.css — Empty placeholder for custom styles.
.nojekyll — Empty file to skip Jekyll processing on GitHub Pages.
posts/_metadata.yml
# Options shared across all posts
freeze: trueNote: freeze: true (not auto) at posts level means posts are NEVER re-executed unless you explicitly render that specific post. The project-level freeze: auto applies to non-post content.
Task 3: Update .gitignore
Add these entries:
/.quarto/
/_site/
Do NOT ignore _freeze/ — it must be committed for the freeze strategy to work in CI.
Task 4: Create GitHub Actions Workflow
.github/workflows/publish.yml
on:
workflow_dispatch:
push:
branches: main
name: Quarto Publish
jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Render and Publish
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}No Python, R, or .NET setup needed — freeze means all computation results are already committed.
Task 5: Create Sample F# Blog Post
posts/hello-fsharp/index.ipynb
A Jupyter notebook with: - Cell 1: Raw cell with YAML front matter (title, description, author, date, categories) - Cell 2: Markdown cell with introduction - Cell 3: F# code cell with a simple example - Kernel metadata: .net-fsharp
The .ipynb JSON must have "cell_type": "raw" for the front matter cell (not markdown).
Task 6: Local Render and Freeze (Manual Steps)
After implementation, the user needs to:
Install prerequisites locally:
# Install Quarto from https://quarto.org/docs/get-started/ brew install --cask quarto # Install dotnet-interactive and register the F# kernel dotnet tool install -g Microsoft.dotnet-interactive dotnet interactive jupyter install # Verify: jupyter kernelspec list should show .net-fsharpRender locally:
quarto renderThis executes notebook cells and creates
_freeze/and_site/.Preview locally:
quarto previewInitial publish (creates
gh-pagesbranch and_publish.yml):quarto publish gh-pagesCommit the generated
_publish.ymlto the repo.Configure GitHub repo settings:
- Settings > Pages > Source: “Deploy from a branch”
- Branch:
gh-pages// (root) - Settings > Actions > General > Workflow permissions: “Read and write permissions”
Commit and push all files including
_freeze/directory.
Critical Files
_quarto.yml— Project config with freeze strategyindex.qmd— Blog listing pageposts/_metadata.yml— Per-post freeze override.github/workflows/publish.yml— CI/CD pipelineposts/hello-fsharp/index.ipynb— Sample F# notebook post.gitignore— Exclude build artifacts, keep_freeze/
Verification
- Run
quarto renderlocally — should produce_site/with rendered blog - Run
quarto preview— should open browser with working blog - Verify
_freeze/posts/hello-fsharp/exists after render - Push to main — GitHub Actions should deploy to gh-pages
- Visit https://everybodykurts.github.io — blog should be live