CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

Render the site locally:

quarto render

Render a single page:

quarto render publications.qmd

Preview the site with live reload:

quarto preview

Refresh the Zotero cache (no API key needed — My Publications endpoint is public):

node scripts/fetch-zotero.js

Deploy to Codeberg Pages (pushes _site/ to the pages branch):

# No publish-codeberg.sh currently exists; deployment is done via Forgejo CI on push to main
git push origin main

Architecture

This is a Quarto static website rendered locally and deployed to Codeberg Pages.

Key files: - _quarto.yml — site-wide config: navbar, theme (sketchy/slate), footer, and the pre-render hook that runs scripts/fetch-zotero.js - posts/_metadata.yml / talks/_metadata.yml — shared frontmatter for all posts/talks (freeze, banner, TOC, citation)

Content sections: - posts/ — blog posts, each in a dated subfolder with index.qmd - talks/ — talk pages with optional slides.qmd (Revealjs); talk listings pulled from talks/talks_*.yml files - publications.qmd — dynamically populated from the Zotero cache at page load - favorites.qmd, workflow.qmd — static pages

Computational freeze: Posts and talks use freeze: true, so R/Python code chunks are not re-executed on render. Frozen outputs live in _freeze/. To re-execute a post, delete its _freeze/ entry.

Zotero integration (two-part): 1. scripts/fetch-zotero.js runs at build time (pre-render hook) and writes all items from the Zotero “My Publications” library to data/zotero-cache.json. It uses If-Modified-Since-Version to skip the fetch when nothing has changed — this detects all change types (add, modify any field, delete), not just new items. Paginates automatically using the Total-Results response header so libraries with >100 items are fully fetched. The cache is also refreshed daily via the Forgejo scheduled workflow (.forgejo/workflows/scheduled.yml), which supports workflow_dispatch for on-demand manual runs from the Forgejo UI. 2. assets/js/zotero.js runs client-side on publications.qmd. It fetches /data/zotero-cache.json, filters by itemType (parsed from the Zotero API URL passed to fetchAndRenderRecords), and renders items into a <ul>. The script path in publications.qmd must be assets/js/zotero.js (relative to site root), not zotero.js.

Deployment: Forgejo CI (.forgejo/workflows/publish.yml) triggers on push to main, renders the site with Quarto, and force-pushes _site/ to the pages branch. Two secrets are required: ZOTERO_API_KEY and CODEBERG_SECRET.

Resources: Static files in data/ and assets/js/ are declared under project.resources in _quarto.yml so Quarto copies them to _site/ without treating them as content to render.

Blog post authoring workflow

Post with R/Python code chunks: 1. Write the post in posts/YYYY-MM-DD-title/index.qmd 2. Render locally to freeze outputs: quarto render posts/YYYY-MM-DD-title/index.qmd 3. Commit the .qmd and the generated _freeze/ outputs 4. Push to main — Forgejo CI renders (using frozen outputs) and deploys

Post with no code (pure markdown): 1. Write the post in posts/YYYY-MM-DD-title/index.qmd 2. Commit and push to main — Forgejo CI renders and deploys directly

Forgejo CI never executes R/Python (freeze: true), so frozen outputs must be committed locally for posts with code.