• People
  • Applications

CLAUDE.md

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

What this is

Static website for PPGCosmo (International PhD Program in Astrophysics, Cosmology and Gravitation at Ufes, Brazil), built with Quarto and deployed to GitHub Pages at ppgcosmo.org.

Commands

pip install jupyter        # one-time: Python + Jupyter are required to execute embedded code
quarto preview             # live preview with auto-reload
quarto render              # full build → _site/

There is no test suite or linter. “Correctness” means the site renders without errors and the pages look right in preview.

Deployment

Pushing to main triggers .github/workflows/publish.yml, which runs quarto render (Quarto pinned to 1.7.32) and publishes _site/ to GitHub Pages. No manual deploy step. If you change how pages render, verify with quarto render locally before pushing, since a render failure breaks the live site.

Architecture

The site is data-driven: .qmd pages contain embedded Python (executed at render time via Jupyter) that reads JSON/CSV from _data/ and prints HTML into the page (#| echo: false + #| output: asis). Editing content almost always means editing a _data/ file, not the pages.

  • Pages: index.qmd (home + news + coursework), people.qmd, applications.qmd (plain Markdown, edit directly), contributors.qmd, wabb2026.qmd.
  • _data/: the content source of truth — people lists (faculty_brazil, phd_students, postdocs, postdocs_previous, alumni, collaborators), institutions, news.json, and the WABB CSVs.
  • _quarto.yml: site-wide config (navbar, footer, flatly theme, page-layout: full). styles.css holds CSS overrides.
  • contributors.qmd builds its table by shelling out to git log --numstat at render time and maps author names → GitHub handles via _data/github_usernames.json. Add new committers there to get them linked. This is why the CI checkout uses fetch-depth: 0 — a shallow clone would silently truncate the table.
  • tools/: local coordination scripts, not part of the site (see below).

The generated HTML depends on class names defined in styles.css (people-grid, person-card, person-name, person-links, person-qualified, person-qualifying-pending, faculty-grid, collapsible-section, table-wrap, prev-coord-table, people-cta, btn-people, btn-ufes, connections/connection-item/conn-*, photo-credit). Changing a class in one place means changing it in both.

news.json entries have three shapes

index.qmd’s news_feed() renders three kinds of entries (see TYPE_COLORS / TYPE_LABELS / ROLE_LABELS in the file):

  1. Call / announcement — has "type": "call" (or "announcement") and a "title". Rendered with a colored type tag.
  2. Person achievement — has a "people" array of {"name", "role"} where role is fac|pd|s|a. No type/title.
  3. Qualification exam — has "type": "qualification" and "student" (a plain name string — no people/role, since the tag already says these are students), plus "title" (the exam title), "when" (free display string, house style "14 August 2026, 16:00 BRT") and "committee" (list of {"name", "affiliation"}, affiliation optional). The student heads the card instead of the title; the title, when and committee are rendered as body lines. date is the announcement date, not the exam date.

Any entry whose type is in TYPE_COLORS gets a tag; the tag text is TYPE_LABELS.get(type, type), which is how qualification displays as “qualification exam”. A typed entry heads with student when type is qualification and with title otherwise — both keys are required, not optional, for their respective shapes.

All shapes use date (ISO "2026-05-15" or year-only "2026"), text (may be ""), and urls (possibly []). Entries are sorted newest-first; the home feed shows only the current and previous calendar year (years=2 cutoff).

people.qmd specifics

  • people_grid() renders any of the people JSONs. It only needs name, photo, and optional url; collapsible= wraps the grid in a <details> (used for Alumni and Previous Postdocs), and filter_fn= selects a subset — the Student Representatives section is phd_students.json filtered on a truthy student_representative flag.
  • phd_students.json carries far more per-person fields than the grid displays (e-mail, orcid, cv_lattes, fellowship, enrolment, advisor, coadvisor, qualifying, country, grad_*, masters_*, slug). Preserve them when editing; CONTRIBUTING.md still documents only the old {name, photo} shape.
  • _data/students_data.csv is the spreadsheet those student records came from. No page reads it — it is not a build input.
  • The Leaflet map is generated from institutions.json + collaborators.json. Each institution tooltip lists faculty by substring match of institutions[].name inside faculty_brazil[].affiliation — so combined affiliations like "CBPF & UNSAM" intentionally match both. Renaming an institution silently empties its tooltip.
  • The Coordination and Previous Coordinators tables are hardcoded Markdown/HTML in people.qmd. _data/organization.json is stale and read by nothing — edit the page, not the JSON.
  • The rotating banner is a hardcoded slides array in the page’s include-in-header script (image, caption, link), not data-driven. Three slides on an 18 s cycle; the caption interval (6000 ms) and the CSS animationDelay (i * 6) must stay in step with the slide count.
  • Card icons (person_links()): e-mail, Lattes, ORCID, website, then the qualifying check mark. Every item is skipped when its field is empty, which is why the same function works for alumni and postdocs, whose records lack these fields.
  • E-mail obfuscation: _quarto.yml’s email-obfuscation: javascript only covers links Pandoc parses from Markdown, not the raw HTML these chunks print. So email_attrs() splits each address into data-u/data-d and the script in this page’s include-in-header reassembles it. Both halves are required — printing a plain mailto: here would publish harvestable addresses.
  • The Lattes icon is referenced as images/lattes.svg rather than inlined on purpose: Pandoc lowercases attribute names when re-serialising HTML, turning an inline viewBox into an ignored viewbox and breaking the icon’s scaling.

Qualifying-exam status

One field drives everything: qualifying in phd_students.json, as "YYYY-MM".

  • On the People page a filled field earns a green check; an empty one earns a grey “to be done” check only for students whose enrolment is on or after QUALIFYING_RECORDS_FROM ("2023-01") — earlier cohorts predate record-keeping and get no mark. That cutoff is a fixed string by design: deriving it from who has already passed would creep forward and silently drop the pending mark from students who still owe the exam.
  • Deliberately only two states, never a “running late” shade — the page is public and flagging named students as overdue exposes more than the programme wants.
  • tools/qualifying_report.py is where that oversight lives: a stdlib-only script (python3 tools/qualifying_report.py) that prints an overdue-first ranking to the terminal and writes nothing. It stays a .py precisely so quarto render cannot pick it up. Its RECORDS_FROM duplicates QUALIFYING_RECORDS_FROM in people.qmd — change one, change the other. tools/README.md documents the groups and settings.

wabb2026.qmd (workshop page)

Event page for the V WABB workshop, written in Portuguese. Mostly hand-written Markdown, plus Python chunks that render _data/wabb/wabb2026_program.csv (schedule) and _data/wabb/wabb2026_registered.csv (registrant list) as Bootstrap tables. Registration itself goes through an external Google Form linked from the page.

Everything specific to the workshop is namespaced so future editions can be added without touching the rest of the site: data in _data/wabb/ (year in the filename) and images in images/wabb/ (v_wabb_header.jpeg, iv_wabb_2025.webp, apoio_wabb.webp). The page also uses images/logo-ppgcosmo_1.webp and images/campus1-4.webp, which stay in images/ as shared assets.

csv_table() is defined once, in a hidden setup chunk above “Lista de inscritos”; the later chunks only call it (Quarto runs all of a page’s chunks in one Jupyter kernel). It must never raise — a missing or renamed CSV prints UNAVAILABLE instead, because an exception here aborts quarto render for the whole site, not just this page. Cells are HTML-escaped and then given back a fixed Markdown subset — [text](url), **bold**, *italic* — which is how **Palestra 1** in the program CSV renders bold. All-empty trailing columns and surrounding whitespace are stripped, so a spreadsheet export padded with ,, still renders cleanly.

Only edition-specific tables are data-driven. Dates, invited speakers, both committees, hotels and fees are deliberately still hand-written Markdown — this is the first WABB edition built in Quarto, and generalising the page into a template is deferred until a second edition actually needs it.

Its navbar entry in _quarto.yml is commented out, so the page renders and is reachable by URL but not linked from the nav. Uncomment those two lines to publish it.

Profile photos

images/people/firstname_lastname.jpg (lowercase, no accents/spaces), referenced by the photo field in the people JSON. Extensions vary in practice (.jpg, .jpeg) — the photo value must match the real filename exactly. Displayed as 90×90 px circles. Use images/people/unknown_person.jpg as the placeholder.

Notes

  • *.quarto_ipynb files are Quarto’s intermediate render artifacts — gitignored, safe to ignore; .quarto/ and _site/ likewise.
  • Quarto renders every root-level .md, not just the .qmd pages: CLAUDE.md and CONTRIBUTING.md are published as CLAUDE.html and CONTRIBUTING.html. (README.md is the one Quarto skips by default, which is also why tools/README.md never renders.) Nothing links to them, but they are publicly reachable — write them accordingly.
  • _quarto.yml’s resources copies images/** and CNAME into _site/; _data/ itself is not copied (only _site/_data/CNAME, which is just the resource keeping its path), so the JSON files are read at render time and never published.
  • CONTRIBUTING.md is the human-facing editing guide and covers the common content edits in more detail.

Links
People
Applications
Cosmo-Ufes
NC3
PPGCosmo bylaws

Contact
PPGCosmo, CCE, Universidade Federal do Espírito Santo
Av. Fernando Ferrari, 514. 29075-910. Vitória, ES, Brazil.

Website
Contributors
Built with
Hosted on GitHub Pages
This website is maintained by the PPGCosmo coordination,
with the help of its students and postdoctoral researchers.