The Personalized Immunotherapy Research Lab website: a small Python/Jinja static site intended for GitHub Pages. Most lab updates require editing YAML, not HTML or Python. Images and fonts are included in the repository; Adobe Garamond currently uses an external Adobe Fonts loader.
- Repository after renaming:
pirl-unc/pirl-unc.github.io - Initial GitHub Pages address: https://pirl-unc.github.io/
- Final domain: https://pirl.unc.edu
- Migration status: first GitHub deployment and domain cutover are pending. The separate hosted prototype is not GitHub Pages.
- Open the relevant file using the table below and click the pencil (Edit).
- Make your change. Keep the existing indentation; YAML uses spaces, not tabs.
- Choose Create a new branch for this commit and start a pull request. Give it a short description, such as “Add Jane to the People page”.
- For photos, upload the image into the appropriate
static/assets/folder on that same branch, then edit the YAML to reference it. Filename case matters. - Ask another lab member to review the pull request. The Check site workflow validates the source and builds it without publishing. It does not create a public preview URL or inspect the visual appearance.
- Merge into
mainwhen ready. Build and deploy publishes the update. Watch the Actions tab; a change is live only after deployment succeeds.
You need repository write access to create branches directly. Otherwise, use a
fork and pull request or ask a repository administrator for access. Direct edits
to main also deploy, so use pull requests for ordinary changes.
| Change | File | Images |
|---|---|---|
| Add a person, change a role, move someone to alumni | data/people.yml |
static/assets/people/ |
| Add or update a publication | data/papers.yml |
static/assets/papers/ |
| Feature software or hide a repository from the list | data/tools.yml |
static/assets/code/ |
| Update addresses, contact emails, investigator portraits | data/contact.yml |
static/assets/contact/ |
| Change navigation, blog URL, number of recent posts, social links | data/site.yml |
Logo paths are in this file |
| Change the homepage introduction | templates/index.html |
— |
| Change footer logo or Adobe Fonts loader | data/appearance.yml |
static/assets/ |
| Change colors, spacing, columns, typography | static/site.css, static/fonts.css |
— |
| Change the shared header or footer | templates/base.html |
— |
Do not edit _site/ or dist/: these are generated and replaced by each build.
data/posts.json and data/repos.json are automatic caches, not the normal
editing interface. Publish blog articles on Substack.
Upload jane-doe.jpg to static/assets/people/, then add an entry under the
appropriate group's people: list in data/people.yml, matching its indentation:
- name: Jane Doe
role: Research Scientist
photo: jane-doe.jpg
links:
- Website: https://example.org/
- Scholar: https://scholar.google.com/
- GitHub: https://github.com/exampleReplace the example links with the person's own profiles, or omit links.
To add a personal research website later, add - Website: https://example.org/
under that person’s links: list. Use the Website label consistently; it appears
alongside Scholar, ORCID, and other profile links. Only add verified personal
research sites, and omit the entry when no site is known.
role is optional. Use a portrait with room around the face: photos are cropped
to 3:4. Keep filenames simple, such as jane-doe.jpg.
To move someone to alumni, move their complete entry into the Alumni group.
Group layout values are investigators, members (default), and alumni;
layout follows the group rather than its position in the file.
Add to the top of papers: in data/papers.yml. This example shows the schema;
replace all example content and supply the image file before merging:
- title: "Example paper title: a descriptive subtitle"
authors: Jane Doe, John Doe
venue: Example Journal
year: 2026
url: https://example.org/paper
images:
- assets/papers/example-paper.png
paragraphs:
- A short summary of the question and principal result.
- An optional second paragraph describing its relevance.Entries without figures use a separated, full-width text layout.
Put the figure at static/assets/papers/example-paper.png. For no figure use
images: []. The template displays title, authors, figures, and paragraphs;
venue and year are metadata and are not currently displayed. Verify the
publication link before merging. Use paragraphs as the single description
source; do not add a duplicate summary field.
Add under featured: in data/tools.yml:
- name: Example Tool
url: https://github.com/pirl-unc/example-tool
image: assets/code/example-tool.png
description: A short explanation of what the tool does and who it is for.Upload the image to static/assets/code/. The separate repository table refreshes
from the pirl-unc GitHub organization. To exclude a repository from that table,
add its exact name to hide_repos, for example hide_repos: [example-tool].
This does not remove a manually featured tool.
data/contact.yml has ordinary sections and a separate investigators section.
Each investigator carries their own image path, so reordering contacts keeps
portraits attached to the correct names.
- Quote text containing
:or#, e.g."Lineberger (LCCC) | CB # 7599". Otherwise YAML can interpret the text as structure or a comment. - Use
>-for a long paragraph wrapped across source lines. Follow the existing indentation. Use a list for separate paragraphs. - Most content is plain text and HTML-escaped.
tools.yml'sintroexplicitly supports HTML; do not paste scripts or untrusted HTML into it. - Store public website content only. The intended repository and website are public.
Requirements: Python 3.12 and Git. No Node.js, database, or application server is needed. Once the repository has been created:
git clone https://github.com/pirl-unc/pirl-unc.github.io.git pirl-website
cd pirl-website
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python -m unittest discover -s tests -v
SITE_URL=http://localhost:8000 python build.py
python -m http.server --directory _site 8000Open http://localhost:8000. Re-run the build after editing; there is no automatic
reload. On Windows, activate with .venv\Scripts\Activate.ps1 and set the URL
using $env:SITE_URL = "http://localhost:8000" before python build.py.
Tests are offline and replace _site/. They check feed/image parsing, cache
fallback, local links, root/project URLs, reordered content, and output-directory
safety. They do not test browser appearance or mobile-menu interactions.
SITE_URL determines navigation prefixes and canonical URLs. To check the
GitHub project path, build with SITE_URL=https://pirl-unc.github.io/pirl-site.
The deployment workflow obtains the actual address from GitHub Pages settings.
OUTPUT_DIR accepts only _site (default) or dist, both disposable directories.
build.py: loads data, sets URL helpers, and renders pages.templates/: one Jinja template per page, extendingbase.html.static/: copied unchanged into the published site.scripts/: refreshes blog/repository caches and imports remote portrait assets.tests/: standard-library unittest tests..github/workflows/check.yml: pull request checks; no publishing or feed writes..github/workflows/build.yml: refresh, build, and publish frommain.
To add a page, create a template extending base.html, add its template/output
path to PAGES in build.py, and add navigation in data/site.yml. Use
site_path('/new-page/') for internal links and asset('assets/...') for local
images so the page works both under /pirl-site/ and at the final domain root.
Add a new YAML file to the loading/context section of build.py if needed.
The deployment workflow runs on pushes to main, manual dispatch, and every six
hours (23 */6 * * *, UTC; scheduled runs can be delayed).
- Run offline tests.
- Fetch Substack RSS and public GitHub repositories.
- Commit changed caches and downloaded covers.
- Build HTML and publish through GitHub Pages.
Covers are extracted from image enclosures, Media RSS, or article HTML in the
feed and downloaded to static/assets/posts/. Posts without art show a short plain-text excerpt from the RSS article body (up to 60 words), falling back to the subtitle when unavailable. Cached excerpts survive feed failures. Posts remain text-only,
with spacing and thin dividers. No substitute logo is used.
If RSS fails, a valid existing post cache remains untouched; a first import with no usable cache fails. Cover failures retain an older downloaded cover when available. A successful deployment can therefore still contain older posts: check the refresh step's warnings if an article is missing. Repository-refresh failures likewise keep the existing repository cache.
To refresh manually, open Actions → Build and deploy → Run workflow (main).
For local maintenance:
python scripts/fetch_posts.py
python scripts/fetch_posts.py --cached-only # download known covers without RSS
python scripts/fetch_repos.pyPublic-repository schedules can be disabled after 60 days without repository activity. Check Actions if updates stop, then re-enable the workflow if needed.
- Create the public repository
pirl-unc/pirl-unc.github.io, withmainas its default branch, and push this source tree, including.github/workflows/and assets. - In Settings → Pages, set Source: GitHub Actions.
- Ensure repository/organization Actions policy permits these workflows and
the built-in token's requested permissions. Deployment needs
pages: writeandid-token: write; cache commits needcontents: write. - Run Build and deploy from Actions. Wait for both build and deploy to succeed, then inspect https://pirl-unc.github.io/ on desktop and phone.
- If branch protection prevents bot cache commits, adjust the update workflow to use a reviewed PR or separate cache storage; do not disable review rules merely to make the bot push. Failed cache pushes currently fail the workflow.
No personal access token is required for scheduled builds: they use GitHub's
built-in GITHUB_TOKEN. Pull requests run read-only checks and cannot publish.
The workflows do not configure branch-protection settings automatically.
Do this after the GitHub Pages address works. Keep Squarespace running during the transition and until rollback is no longer needed.
DNS checked on 2026-10-15:
| Name | Type | Existing target | New target |
|---|---|---|---|
pirl.unc.edu |
CNAME | ext-cust.squarespace.com |
pirl-unc.github.io |
The observed DNS TTL was 3600 seconds. Recheck before cutover; the DNS administrator may be able to lower it in advance.
- In the pirl-unc organization Settings → Pages, add
pirl.unc.eduas a verified domain. GitHub supplies an exact TXT name/value. Ask the existing UNC DNS administrator to publish it, then verify it in GitHub. Keep that TXT record. This is separate from the repository's custom-domain setting. - In the repository Settings → Pages, save Custom domain:
pirl.unc.edubefore switching the CNAME. This may redirect the default GitHub Pages URL, so do it as part of the planned cutover after reviewing that URL. - Re-run Build and deploy after setting the custom domain, so generated asset
paths use the domain root instead of
/pirl-site/. - Ask the UNC team managing this hostname to replace its current CNAME with
pirl-unc.github.io. Do not includehttps://,/pirl-site/, or a trailing URL path. Do not change the nameservers or records for the rest ofunc.edu. - Wait for GitHub's DNS check and certificate provisioning; enable Enforce
HTTPS when available. DNS propagation and certificate issuance can take
time. Check the final HTTPS URL, all navigation, images, and
/research/. - Keep the old site available until the new site has been checked on desktop and mobile, and fonts and feed updates work independently of Squarespace.
GitHub Actions publishing does not require a source-tree CNAME file; configure
the custom domain in Pages settings. Domain control is separate from GitHub
repository access: UNC's DNS administrator must make the DNS change.
Suggested text for the UNC DNS request (send only when the GitHub site is ready):
Please coordinate moving pirl.unc.edu from Squarespace to GitHub Pages for the Personalized Immunotherapy Research Lab. Replace its CNAME target ext-cust.squarespace.com with pirl-unc.github.io. Please preserve unrelated UNC DNS records. We will first provide GitHub's domain-verification TXT record for publication and verify it, configure the repository's custom domain, and agree on a cutover time. The current observed TTL is 3600 seconds; can it be reduced in advance? Please retain the old target for rollback.
We have not confirmed the internal UNC owner of this DNS record. Start with the team that configured the Squarespace mapping or your departmental IT contact.
| Symptom | What to check |
|---|---|
| YAML/build failure | Open the failed Actions step; check indentation, quotes, and required fields. The previous deployment remains live if a new deployment fails. |
| Change not visible | Verify the edit merged into main, deployment succeeded, and the browser is not showing a cached copy. |
| Broken photo | Check exact filename/case, folder, and YAML path. Uploaded files must be on the same branch. |
| New post missing | Inspect RSS refresh warnings; cached posts may be served during an outage. |
| Pages 404 | Check Pages source, successful deploy job, repository path, and custom-domain setting. |
| Styles missing after cutover | Re-run deployment with the custom domain saved; confirm generated links no longer start with /pirl-site/. |
| Certificate pending | Check the CNAME and GitHub Pages DNS status; allow provisioning time. |
For a content mistake, revert the merged pull request in GitHub (or use
git revert <commit> on a new branch), review, and merge. This creates a new
commit and redeploys without rewriting history.
For a failed domain cutover, ask the DNS administrator to restore the previous
CNAME ext-cust.squarespace.com while Squarespace remains active. DNS caches can
retain the new record until its TTL expires. To restore the GitHub project URL
as well, remove the custom domain in Pages settings and rerun deployment.
- Verify a successful live RSS fetch in GitHub Actions; the original preview environment returned an HTML “Site Unavailable” page, so its cache was seeded.
- Review desktop/mobile layout, keyboard navigation, and contact details.
- Confirm Adobe Garamond's external font loader works on the final domain or replace it with an appropriately licensed font. Other bundled fonts are local.
- Verify imported publication URLs and image permissions before cutover.
- The old contact form is not migrated; the Contact page lists email addresses.
References:
Software images are optional: omit image for a text-only entry. Do not substitute
the lab logo for missing artwork. Featured software is curated in data/tools.yml;
the automatic public repository table is separate.