This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Course website for the UF "Ecological Forecasting & Dynamics" course (https://course.naturecast.org), taught by Morgan Ernest and Ethan White. It is a Quarto website. The repository is almost entirely course content (markdown lessons + data files); there is no application code, no test suite, and no linting.
The site was migrated from Hugo + HugoBlox (Wowchemy) to Quarto. Anything still referring to Hugo, Wowchemy, HugoBlox, widgets, or a content/ directory is stale.
paper.md / paper.bib are the JOSE paper describing the course; they are not part of the site build.
quarto preview # local preview with live reload
quarto render # production build into _site/
Rscript scripts/build_schedule.R # regenerate the schedule table by hand (rarely needed)Building requires Quarto (1.8 or newer) and R. R is needed only for the schedule pre-render script, which uses base R and no packages — the R tutorials are still not executed, see "R code is not executed" below.
Deployment runs in GitHub Actions (.github/workflows/publish.yml), not on Netlify: it installs R and Quarto, renders, and uploads _site to Netlify. Pushes to main publish to production; pull requests get a preview deploy commented on the PR. netlify.toml sets ignore = "exit 0" so Netlify skips its own build, which would fail for lack of R. Requires NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID repository secrets.
Each lesson is a directory in lessons/<lesson-slug>/. Create new ones by copying lessons/LessonTemplate/, which defines the standard set of pages and their order within a lesson:
| File | order | Purpose |
|---|---|---|
index.qmd |
— | Lesson landing page: title, learning-objectives callout, listing: of the lesson's pages |
material.qmd |
1 | Readings, data downloads, software setup |
discussion_questions.qmd |
2 | Conceptual lessons only |
r_tutorial.qmd |
3 | R lessons only |
instructor_notes.qmd |
4 | Conceptual lessons only |
Two lesson flavors exist and are not mixed: conceptual lessons (paper discussion → discussion_questions.qmd + instructor_notes.qmd) and R tutorial lessons (slug prefixed R- → r_tutorial.qmd).
Front matter is title, optional description (shown in listings), and order. The order on a lesson's index.qmd controls its position on the Lessons page; it is independent of the teaching order on the Schedule page.
Adding a lesson requires two edits: create the folder, and add a section: entry to website.sidebar in _quarto.yml. The sidebar is explicit rather than auto-generated because teaching order is not alphabetical. The Lessons landing page (lessons/index.qmd) picks up new lessons automatically via its listing: sorted on order.
schedule/schedule.csv is the source of truth: one row per class meeting, with columns date, lesson, kind.
kind: lesson—lessonmust exactly match a lessonindex.qmd'stitle:, and renders as a link.kind: event— a meeting with no lesson page (project work days, presentations), rendered as plain text.
scripts/build_schedule.R reads the CSV and writes schedule/_schedule_table.md, which schedule/index.qmd pulls in with {{< include >}}. The Description column is pulled from each lesson's description: front matter rather than stored in the CSV, so editing a lesson's description updates both the Lessons page and the Schedule. Rows with kind: event get a blank description.
You do not need to run the script by hand. It is wired up as pre-render in _quarto.yml, so every quarto render and quarto preview regenerates the table from the CSV first. An unmatched title exits nonzero and aborts the entire render, which is deliberate — the Hugo version this replaced silently degraded typo'd titles into greyed-out placeholder rows.
Two things to know about this setup:
- The generated file is still committed. Quarto resolves
{{< include >}}while enumerating project inputs, which happens before pre-render runs, so the file has to already exist. If it ever goes missing the render fails with "could not find file" — runRscript scripts/build_schedule.Ronce to bootstrap it. CI separately fails the build if the committed copy is stale relative to the CSV. - The script is base R with no packages and no YAML parser (it reads
title:with a regex), so CI only has to install R itself.
execute: enabled: false in _quarto.yml. R chunks are syntax-highlighted and displayed but never run, which matches the pre-migration behaviour — the site has never shown computed output or plots, and there are no figures in the lessons.
Building still requires the knitr and rmarkdown packages. Disabling execution does not stop Quarto from selecting the knitr engine for any file containing ```{r} chunks, and that engine has to be installed to process the file at all. Six tutorials currently trigger this. CI installs the two packages explicitly; locally you will usually already have them. Setting engine: markdown in _quarto.yml does not avoid this — project-level engine is not honored for engine selection.
Consequences: the build needs no R and no package installs, and code that reaches the network (there is a read.csv() from a raw GitHub URL in the EDM tutorial) cannot break a deploy. Turning execution on later means installing every package the tutorials use (mvgam, the EDM stack, and so on) and almost certainly adding freeze: auto with a committed _freeze/ directory so CI doesn't re-run slow models.
data/*.csv (and .R, .zip) are published at /data/<filename> via the resources list in _quarto.yml. Link them from material.qmd as /data/portal_timeseries.csv.
-
Callouts:
::: {.callout-note}…::: -
Math: plain LaTeX,
$inline$and$$block$$ -
Table of contents: automatic (
toc: true), no per-page markup needed -
Leave a blank line before any list, heading, or
---rule. Pandoc is stricter than Hugo's Goldmark about blocks interrupting a paragraph, and all three failure modes are silent:**Learning Objectives:**followed directly by* item→ one run-on paragraph with literal asterisks.- A
## Headingdirectly after a paragraph or bullet → swallowed into that block and shown as literal## Headingtext. - A
---rule directly after a line of text → read as a setext underline, turning that text into a heading.
The whole corpus was swept for these during the migration. Watch for them when editing.
Lesson landing-page listings show only the page title. Quarto falls back to dumping a page's body text into a listing's description column whenever that page has no description: front matter, and an explicit empty string does not suppress it, so the column is omitted rather than showing whole documents.
Hugo published each page as a directory (/lessons/x/material/); Quarto publishes a file (/lessons/x/material.html). Lesson landing pages and top-level sections still resolve natively through index.html, so only child pages needed mapping. netlify.toml holds 301 redirects from the old paths — keep them, the site is linked from a published JOSE paper and from Canvas.
Adding a new kind of lesson page (a name other than material / discussion_questions / instructor_notes / r_tutorial) does not need a redirect, since no old URL exists for it.
lessons/R-state-space-models-1/r_tutorial_new.qmdandr_tutorial_new_notes.qmdare raw R with no front matter — work-in-progress scratch files. They are excluded from the render in_quarto.yml. Hugo used to publish them as broken untitled pages.scripts/build_schedule.Ris the only script in the repo, and it is wired into the render. The one-shot helpers used to perform the Hugo-to-Quarto conversion were deliberately not kept.Contributing/(site page) is not in the navbar and largely duplicatesCONTRIBUTING.md.- This working tree is inside a Syncthing share. Bulk file moves can race with Syncthing and produce
*.sync-conflict-*copies; if that happens, restore from git and delete the conflict files.