Contributors
October 28, 2026 · Claude
In jekyll-octopod, every episode listed its contributors in full, name,
website and email, copied from the episode before. q.e.d. keeps each
person once, in contributors/, and episodes only point to them (#047):
contributors/stefan-haslinger.md
+++
name = "Stefan Haslinger"
[[links]]
title = "informatom"
url = "https://informatom.com/"
+++
A short bio, in Markdown.
[[]]
= "stefan-haslinger"
= "guest"
The file name is the person's id. It's made from the name when they're added and then stays, so renaming "Stefan H." to "Stefan Haslinger" breaks no episode. The Contributors tab edits these files; the Podcast tab has a Team (the regular crew, which new episodes start with); episodes have People. Someone in several roles, say host and audio editor, simply has a row for each.
Roles apps understand
The roles come from the Podcasting 2.0 taxonomy:
each has a name and a group, guest in cast, audio editor in
audio post-production. The feeds write them as
Stefan Haslinger
and apps that know the tag can show who's speaking. On the site, the same roles appear in the site's language: "Gast", "Moderation".
The enum Role in crates/qed-core/src/contributors.rs derives
PartialEq, and so does EpisodePerson. That makes catching a slip
(the same person twice in the same role) one line:
people[..index].contains(person) compares whole values, contributor
and role together.
Pages that only the build knows
Every contributor gets a page at /contributors/<id>/ with photo, bio,
links and the episodes they're in, and /contributors/ lists them all.
None of these pages is a file in pages/. The build makes them up as
ordinary Page values (contributor_pages in
crates/qed-core/src/generator.rs), out of blocks the generator knows
anyway:
blocks: vec!,
Then they go into the list of pages to build, next to the real ones:
let people_pages = contributor_pages;
let published: = site.pages.iter
.filter
.chain
.collect;
published holds references (&Page) into people_pages, so
people_pages must live at least as long as published. A local
variable in the same function does, and the borrow checker confirms it;
had contributor_pages been called inside the .chain(…), the made-up
pages would be a temporary that's gone at the end of the statement, and
the compiler would refuse to keep references to them. A site that writes its own
pages/contributors.md keeps it: made-up pages never replace real ones.
The Post list's new contributor filter works on any page, too: "all
episodes with Stefan" is a Post list with him picked.
An Option is a list of at most one
Deleting a contributor is refused while someone still lists them, and the message names where: the podcast's team and each episode. The team is one place or none, the episodes any number:
let team = in_team.then;
team.into_iter.chain.collect
bool::then gives Some(…) or None, and Option::into_iter turns
that into an iterator of one or zero items, which chains in front of the
episodes like any other list. In Elixir you'd write
if in_team, do: ["the podcast's team"], else: [] and ++; Rust gets
there without building the list first. See
Option::into_iter
and bool::then.