From de640b04de753b26799f9c656e8632162a9b6e44 Mon Sep 17 00:00:00 2001 From: David Arcos Date: Thu, 8 Oct 2026 11:15:08 +0200 Subject: [PATCH 1/4] Tell a person how to change or remove their own card The contributing guide was written for someone who wants to change the site. The people most likely to need something from it are the ones the site is about, and for them the answer is an email, not a pull request. Three sections say so. What is on a card, which is a name, a role, the events computed from the pages that list the person, and nothing else unless they gave it. How to change it, which is an email to the association or the pencil icon on GitHub, with no identity document asked for. And how to have it removed, with what removal can and cannot do on a public repository: the card goes from the site and from the build, and we ask the search engines and the Internet Archive to drop their copies, but the Git history keeps it and so does anyone who already copied it. Whoever is not happy with that should tell us before we publish, and we would rather not publish a card than publish one we cannot withdraw. The text comes from a draft written for the Archive, where every person was going to have a page of their own at a URL. They do not: config.toml cascades render = "never" over content/people, so a person file builds no page. The draft also named a short_bio field that no longer exists, a content/talks section that was never created, and a privacy policy at a path the site does not serve. All of that is rewritten to the site as it is. --- CONTRIBUTING.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 152a533f..e6b672f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,51 @@ the source. `master` holds the built site: the `github-pages` workflow writes it on every merge to `edition` and replaces its history each time, so nothing is edited there, and a change made on `master` is lost on the next deploy. +## Your own card + +**You do not need Git, GitHub, or any of the rest of this file.** + +If you have spoken at one of our events, or you organise with us, you may have +a card on the [organizers page](https://pybcn.org/pybcn_association/organizers/) +or on the page of the event. Clicking it opens what the site holds about you. +To change any of it, or to have it removed, **send an email to + saying what to change**. That is enough. We do not ask +for identity documents to correct or remove your own name. + +If you would rather do it yourself, every card is a file under +`content/people/`, named after the person. Click the pencil icon on GitHub, +change the text, and GitHub walks you through opening a pull request. + +### What is on your card + +Your name, the role you held, and the events you appeared at. The list of +events is computed from the pages that list you, so it is not edited on your +card: if an event is missing or wrong, tell us and we fix the event page. + +Everything else is yours to give or withhold: + +| Field | What it does | +|---|---| +| `photo` | A square image, under `themes/pybcn_theme/assets/images/people/` | +| `linkedin`, `github`, `twitter`, `site` | Links under your name | +| the body of the file | The bio, one paragraph or several | + +Whoever did not want a photo does not have one, and the card works without it. + +### If you would rather not appear + +Write to and we remove the card. We will not argue +about it. + +Be aware of what removal can and cannot do, because this is a public +repository. We remove the card from the site and from the build, and we ask +search engines and the Internet Archive to drop their copies. We cannot remove +it from the Git history, and we cannot remove copies other people have already +made. + +If that is not acceptable to you, tell us **before** we publish. We would +rather not publish a card than publish one we cannot properly withdraw. + ## Add content without writing code Most changes to this site are content: a person, a sponsor, an event. Each one From e943a4fe5de5e0d10b8c33f73fd2359f253c1ab6 Mon Sep 17 00:00:00 2001 From: David Arcos Date: Thu, 8 Oct 2026 11:15:08 +0200 Subject: [PATCH 2/4] Add AGENTS.md, for the agents that work on this repository A file for AI coding agents, beside the contributing guide for people. It holds what an agent gets wrong that a human would not, and what this repository does differently from a stock Hugo site: the pinned binary at bin/hugo and why not to pip install hugo, the TOML key that lands inside the last table and silently does nothing, merge.ff = only, unsafe = true in the Goldmark renderer and the two content files that still carry raw HTML, images under assets rather than static, and master being the built site rather than a source branch. The draft was written on 2026-10-03 and four of its facts had gone stale, so each one was checked against the tree before this commit: - The page count was 310. A clean build gives 317: 261 from Hugo and 56 copied from static/archives. The line now says to recount rather than trust it. - It said resources/_gen is tracked in Git and that a build dirties the tree. Both it and docs/ are gitignored now, so the line says what a dirty tree there actually means: a branch from before that change. - It named the two files that need unsafe raw HTML without saying what guards them. bin/check-html-safety and bin/check-rendered do, on every pull request, and both are now in the file. - bin/serve takes no port: it runs Hugo's own default. The table of paths gains data/ and a table of the three checks, which are what a pull request has to pass. --- AGENTS.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..28a41a04 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,166 @@ +# AGENTS.md + +Guidance for AI coding agents working on pybcn.org, the website of Associació +Python Barcelona. + +Read [CONTRIBUTING.md](CONTRIBUTING.md) as well. This file covers only what an +agent gets wrong that a human would not, and what this repository does +differently from a default Hugo site. + +## Build and run + +```sh +bin/install # downloads the pinned Hugo binary, verifies its SHA256 +bin/serve # dev server, Hugo's default :1313 +bin/build # writes to docs/ +bin/hugo --quiet -d /tmp/out # the binary directly +``` + +**The Hugo binary lives at `bin/hugo` and is gitignored.** `bin/helpers` prefers +it over anything on `$PATH`, and checks the version from `.hugo-version`. + +**Do not `pip install hugo`.** That PyPI package now requires a Go toolchain and +fails. `bin/install` fetches the official release binary instead. + +A clean build produces **317 HTML pages**: 261 Hugo-generated and 56 copied +from `static/archives/`. If your number differs, work out why before you +commit. Recount rather than trusting this line: the number moves with the +content, and a stale figure here is worse than none. + +## Traps that have already cost time + +**TOML key placement.** `config.toml` ends with nested `[params.social_items...]` +tables. A key appended at the end of the file lands **inside the last table** and +silently does nothing. Put top-level keys near `theme =`, and verify by building +and checking the output, not by reading the file. + +**`merge.ff = only`.** Any local merge needs `--no-ff`. A failed merge here reports +"Not possible to fast-forward", which is not a conflict. + +**`unsafe=true` in `[markup.goldmark.renderer]`.** Raw HTML in front matter +and page bodies renders unescaped. Two content files still carry it, +`content/events/pyday_bcn/pyday_bcn_2018.md` and `pyday_bcn_2019.md`, which +are pre-Hugo programmes pasted in as tables. So **assume any content field +can inject HTML**, and never add a field that reaches a template through +`safeHTML`. `bin/check-html-safety` rejects dangerous markup in content and +`bin/check-rendered` checks the built pages; both run on every pull request. + +**`resources/_gen/` and `docs/` are build output and are gitignored.** They +used to be tracked, so a build dirtied the tree and a pull request carried +hundreds of regenerated files. If `git status` shows either of them, you are +on a branch from before that change. + +**`master` is not a source branch.** It is the built site, force-pushed as an +orphan commit by the deploy workflow. The default branch is **`edition`**. + +**Images in `static/` cannot be processed by Hugo.** They must be under `assets/` +for `resources.Get` and `.Resize` to work. This is why the site once shipped 8.97 +MB of photographs on a single page. + +## Rules that are not style preferences + +These come from decisions with reasons behind them. Breaking them creates a +problem somebody else has to find. + +**Never store a role that can be computed.** `speaker` and `host` are derived from +the talk and meetup records. Writing them into a person's front matter guarantees +they will eventually contradict the data. + +**A person attribution requires human verification.** Talk records carry +`source` and `confidence`. **Only a human-verified record may create a person page +or link a speaker name.** A record with `confidence: medium` or `low` renders the +talk with an "unconfirmed" marker and leaves the speaker name as plain text. + +This is not a style rule. Publishing an unverified claim about a named person +fails the accuracy principle of the GDPR, and the confidence field then documents +that we knew. + +**Never merge two people on a first-name match.** The existing ids include bare +first names (`david`, `alberto`, `jordi`, `ricardo`) that already collide with +fuller names on the site. When in doubt, leave both and flag it. + +**Do not promote hosting to a role.** Meetup's `eventHosts` field is a tool +attribute, not a title. Hosting one meetup is not being an organizer. + +**RSVPs are not attendance.** `attendedCount` is zero on 205 of 207 events, so the +only figure available is who said yes on Meetup. Render it as registrations. + +**No personal data beyond what is already published.** Never render email +addresses, phone numbers, or social handles extracted from old event +descriptions, even though they are present in the source data. Never derive a +person's attributes from the event they spoke at. + +**Person ids** are `firstname-surname`, kebab-case, accents stripped. A rename +carries an `aliases` entry **and** updates every reference in `content/events/` in +the same commit. Skipping the second half silently drops the person from those +pages, and that bug has been live on this site. + +## Commits + +**No AI attribution.** No `Co-Authored-By` trailer, no generated-with footer, no +model byline. Commits read as authored by the person who made them. + +**Never use the em dash character (U+2014).** Use a comma, a colon, parentheses, or +restructure. + +Write in plain, factual English. Say what changed, why, and what you verified. +**If part of the work is unfinished, say so in the commit message**, with what +blocks it. A commit that claims more than it did is worse than one that admits a +gap. + +## Verify, do not assert + +This repository has a history of changes that looked right and were not. The +build succeeding proves very little: a missing sponsor page, a person id that does +not match its filename, and a broken image URL all build cleanly. + +So: + +- Build and grep **the generated HTML**, not the templates. +- Give before and after numbers for anything you claim to have fixed. +- When you cannot verify something without a browser or a screen reader, **say + that** rather than asserting it works. + +Two checkers exist and both are cheap to run: + +```sh +bin/check-content # front matter, ids, cross-references (needs pyyaml) +bin/check-html-safety # dangerous raw HTML in content +``` + +## Things you cannot do + +**Do not push, open pull requests, or comment on GitHub.** Leave your work as a +local branch. Opening it is a human decision, taken per item. + +**Do not change DNS, repository settings, or anything outside the working tree.** + +**Do not install software.** If a task genuinely needs a tool that is absent, say +so and stop, rather than working around it with something that produces a +plausible but different result. + +## Where things are + +| Path | What | +|---|---| +| `content/people/` | One markdown file per person, keyed by `id` | +| `content/events/` | PyDay BCN, PyDataBCN, and other events, with their agendas in front matter | +| `content/sponsors/` | One file per sponsor | +| `themes/pybcn_theme/` | The theme, **vendored in-tree**, not a submodule | +| `themes/pybcn_theme/layouts/partials/` | Most of the interesting template logic | +| `static/archives/` | Verbatim mirrors of pre-Hugo sites, copied unprocessed | +| `data/` | Tables the templates read: `roles.yaml` maps a heading to a role, `role_emoji.yaml` a role to a glyph | +| `bin/` | Setup and build scripts, plus the gitignored Hugo binary | +| `.hugo-version` | The pinned Hugo version, single source of truth | + +The checks, which are what a pull request has to pass: + +| Script | What it rejects | +|---|---| +| `bin/check-content` | A broken reference, a photo that is not square, a source far larger than the build asks for, a field that no longer exists | +| `bin/check-html-safety` | Dangerous raw HTML in content, with an allowlist for the legitimate embeds | +| `bin/check-rendered` | The same, on the built pages, plus any `http://` URL. Run it against a directory you built, never against `public/` while a dev server is writing there | + +The join between a person and an event is `where $pages ".Params.id" "eq" $person` +in `partials/people-grid.html`. That pattern is how everything cross-references, +and it fails silently when an id does not match. From 31791960d86eb1fc0ac7c44c15fd6874b9bab9f7 Mon Sep 17 00:00:00 2001 From: David Arcos Date: Thu, 8 Oct 2026 20:19:09 +0200 Subject: [PATCH 3/4] Correct the page count in AGENTS.md It said 317, which was true when the file was written and stopped being true the moment #202 merged: that pull request stopped publishing the 206 person and sponsor pages, so a clean build is 109 pages now, 53 from Hugo and 56 copied from static/archives. The line already told a reader to recount rather than trust it. It now also says what happened, because a figure that went stale in a day is the best argument for recounting. --- AGENTS.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 28a41a04..943307ef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,10 +22,11 @@ it over anything on `$PATH`, and checks the version from `.hugo-version`. **Do not `pip install hugo`.** That PyPI package now requires a Go toolchain and fails. `bin/install` fetches the official release binary instead. -A clean build produces **317 HTML pages**: 261 Hugo-generated and 56 copied -from `static/archives/`. If your number differs, work out why before you -commit. Recount rather than trusting this line: the number moves with the -content, and a stale figure here is worse than none. +A clean build produces **109 HTML pages**: 53 Hugo-generated and 56 copied +from `static/archives/`. Recount rather than trusting this line: the number +moves with the content. It was 317 until #202 stopped publishing the 206 +person and sponsor pages that nothing linked to, which is how quickly a +figure like this goes stale. ## Traps that have already cost time From 86317731df62b0369586a10ba0ac3ca04224848a Mon Sep 17 00:00:00 2001 From: David Arcos Date: Fri, 9 Oct 2026 10:42:22 +0200 Subject: [PATCH 4/4] Index the decisions, where they are instead of copying them This repository explains itself in the file that carries the decision: the cascade comment in config.toml says why a person file builds no page, the comment in nav.html says why the menu is not an ARIA menu, LICENSING.md argues the licence. That is the right place, because the reason is then in front of whoever is about to change the thing. The cost is that the reasons are scattered and only findable by someone who already knows where to look. So this is an index and not a copy: a table of seven decisions and the file that argues each one. Every path in it was checked against the tree. One of them has no file, because it is a decision not to act: the heavy image blobs stay in the Git history. That one is written out here in full, because a decision not to do something is the kind that gets forgotten and redone. It says what the repository weighs, that the blobs are reachable from edition and not from master, what a filter-repo would cost everyone, and what would have to be true before it is worth doing. Not an ADR folder. Seven decisions and three people do not need one, and a document beside the code is a second place for a reason to go stale in. --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index d606f222..909dc6e0 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,37 @@ people_sections: - josep ``` +## Decisions, and where each one is written down + +This repository explains itself in the file that carries the decision, not in +a folder of documents beside the code. That keeps the reason next to the thing +it governs, and it means the reason is in front of whoever is about to change +it. The cost is that the reasons are scattered, so this is the index. + +| Decision | Where it is argued | +|---|---| +| A person file builds no page, and `/people/` is a 404 | The `[[cascade]]` comment in `config.toml`, and issue #201 | +| An appearance line calls a page by `appearances_title`, not by its title | `themes/pybcn_theme/layouts/partials/appearances_index.html` | +| The modal is on its way to being a page per person, so its content cannot depend on the page it was opened from | `themes/pybcn_theme/layouts/partials/person_appearances.html` | +| The menu sections are a disclosure, not an ARIA menu | `themes/pybcn_theme/layouts/partials/nav.html` | +| MIT for the code, CC BY-SA 4.0 for the content, and nothing for the photographs | [LICENSING.md](LICENSING.md) | +| `edition` is the source branch and `master` is the built site | [AGENTS.md](AGENTS.md) | +| Raw HTML in content is gated by three checks rather than banned | [CONTRIBUTING.md](CONTRIBUTING.md) and `bin/check-html-safety` | + +One decision has no file of its own, because it is a decision not to act: + +**The heavy image blobs stay in the Git history.** The repository is 139 MB, +and the largest objects in it are photographs that were replaced long ago, +up to 5.9 MB each. They are all reachable from `edition` and none from +`master`, which the deploy workflow force-pushes as a single orphan commit. +Removing them means `git filter-repo` and a force-push, which changes every +commit hash from the first touched blob onwards: every clone breaks, every +open pull request has to be rebased, and every link to a commit or to a line +of code goes dead, including the ones in our own issues. GitHub starts warning +at 1 GB. The cost is a one-time clone, not a recurring one, so the answer is +not now. If it is ever done, the moment is an empty pull request queue and a +day when everybody can re-clone together. + ## Licence The code of the site (the templates, the stylesheets, the scripts, the