Skip to content

Phase 4: the site the archive deserves - #220

Merged
DZPM merged 16 commits into
editionfrom
work/phase-4-final
Oct 19, 2026
Merged

DZPM merged 16 commits into
editionfrom
work/phase-4-final

Conversation

@DZPM

@DZPM DZPM commented Oct 18, 2026

Copy link
Copy Markdown
Member

Phase 4 of the site: the archive becomes findable, the home page is rebuilt around it, and what the archive showed about the rest of the site is fixed.

Thirteen commits on top of edition, each one a theme with its reasons in its message. The tree is the one verified: bin/check-content 0 errors, a clean build with drafts, and bin/check-rendered and bin/check-html-safety both green.

The search. /search/ searches the whole archive with no account, no third party, and no request that leaves this site: Pagefind runs over the built pages in CI and the index ships with them. The box is in the header of every page, and a missing address searches the words of its own path, so an old link to a talk lands on that talk. 1,258 pages indexed; opening /search/ costs 176 KB of engine and filters, and a first search 36 KB more.

The home page, the bar, and the footer. The home page says what PyBCN is and where the next event is, in one screen: the year it started, the four counts of the archive, the video, the two Meetup groups, the recent events, the photographs, the board and the committee, the four ways to take part, and the communities next door. The bar and the footer of the whole site follow it. The hero band is gone from every page: a third of the screen with a photograph that said nothing about the page it stood over, and thirteen events shared seven of them.

Finding a person, a talk, a day. /talks/ filters by type (398 talks, 172 workshops, 27 lightning talks, 7 keynotes, 3 panels) and /people/ by role (the board, the committee, 23 organizers, 356 speakers, 10 mentors, 8 collaborators, 1 volunteer), each on its own page, with no JavaScript. /people/role/assistants/ says that the association never recorded who came, which is the honest answer to the question that tab asks.

The photographs. 530 of them on 84 event pages, where the archive had 235 on 11, and 257 name the talk they show. They were in the X export with nobody reading them. What places one is the text of its thread first and the day second; what thanks a sponsor, announces something, or says nothing about the day it was posted on stays out. They are stored as WebP: 23.2 MiB of JPEG become 13.6.

What the archive showed about the rest of the site. /sponsors/ is read from the records and not from a list by hand, 81 and not 46, with 25 that were on an edition page and nowhere else. The sponsors of PyDay BCN 2018 and 2019 come back from the frozen site. 42 PyLadies BCN workshops are named after their day and not after their one talk. The minutes of the eight general assemblies have a section of their own.

Accessibility, privacy, and weight. The schedule of an event reads on a phone. A shared link gets a card with the right title and a picture. No page sends a request to another site to draw itself. The site declares itself light, so a phone does not darken it and leave 81 sponsor logos unreadable. The images of the repository are WebP where they were the wrong format: 8,460,730 bytes of PNG become 2,150,520, and 1,855,481 of those are the frozen snapshots, which keep their files as they were served.

What is worth a look: the new photographs, in case one of them should not be published; the page of each role of /people/, in case a name does not belong there; and the minutes of the assemblies.

Closes #219
Closes #212
Closes #172
Closes #171

DZPM added 13 commits October 18, 2026 11:58
bin/new-meetup writes the page of one meetup from its Meetup URL. The
public page of an event carries the whole event as JSON in its
__NEXT_DATA__ script: the title, the day, the venue, the group, and the
number of people who said yes. No API key, no login. --discover names
the meetups of both groups that have no page yet and prints the command
for each; --dry-run prints the page without writing it; --description
prints the announcement, which is what a model reads to extract the
talks.

bin/import-meetups records where a page came from, so a page written by
hand and a page written from the export say so in the same words. The
output of the importer is unchanged: verified byte for byte on all 266
files it writes.

The links job of a pull request checks the internal links only. The site
holds 139 distinct external URLs outside the frozen snapshots, and
checking them took 25 minutes on the pull request that added the person
pages: every pull request asked every host we have ever linked to
whether it is still there, and reported rot that is not ours to fix. An
internal link that does not resolve is a typo, is ours, and is found in
seconds. The external ones move to .github/workflows/links.yml, which
runs on request with a cache of a week and does not fail the build.

The data of the association: the glyph of each role, the emoji of each
event type, and the assemblies with their minutes.
The site holds 607 talks, 371 people, and 281 events, and the only way
to find one was to know which listing it was on. /search/ searches all
of them.

Pagefind reads the pages Hugo wrote, after the build, in CI and
locally, and ships its index with them: no account, no third party, and
no request that leaves this site. The binary is pinned the way Hugo is,
with the version in .pagefind-version and the SHA-256 of the release
checked by bin/install-pagefind.

What is indexed is the record of each page and not the whole of it: the
person pages repeat the title of every talk the person gave and the
event pages repeat the programme, so a name or a technology would match
three pages per talk. The h1 of an indexed page carries the kind, which
is what a result is labelled by and what the kind filter passes.

The box is in the bar of every page, and the page that runs the search
is the only one that ships the script. A missing address searches the
words of its own path, less the section names: an old link to
/talks/2019/keep-your-repo-clean/ asks for "2019 keep your repo clean"
and the first result is that talk on its new address.

Measured on a production build: 1,258 pages indexed in about a second,
903 KB on disk under /pagefind/, and 209 KB downloaded by a first
search, with the 30 fragments of the results drawn. The second query
loads only the chunk of its word.
/sponsors/ carried its own list of 46 ids by hand, and it had drifted:
25 sponsors were on the page of their edition and nowhere else, among
them 8 of the 12 of PyDay BCN 2025, 15 of 2024, and 2 of 2023. The page
reads content/sponsors/ now, so a sponsor reaches it by existing. The
count in the heading comes from the same walk the grid does, so the two
cannot disagree.

Two pages use that layout, and the second one is why the directory is
read only when the page declares no levels of its own:
/pyladies_bcn/sponsors/ lists the 18 of PyLadies BCN and was given all
71 for one build.

The sponsors of PyDay BCN 2018 and 2019 come back. The two pages
carried them as the HTML of the frozen site of that year pasted into
their body: six img tags in 2018 and nineteen in 2019, each pointing at
/archives/ and each with its own size in a style attribute, from 350px
to 70px. They are levels now, with the levels the site of each year
published, so they take the same card as every other edition. Ten of
those sponsors had no record at all, and three of the ten have no web
address because the company is gone: the record says so rather than
guessing a URL.

The logo of Fundación Caja de Ingenieros was labelled "Fundación
Cibervoluntarios" in the alt text of that pasted HTML, which is a
different organisation. The picture says Caja de Ingenieros.

Every card is one size and every logo sits in the same box, because
with 81 of them the eye reads a difference in size as a difference in
what the sponsor gave. Six to a line at 1,200px and above, two on a
phone; the card is 180x110 and the logo box 150x90. The hover is the one
a person card has.

Each logo is stored at the size the page serves at most: 28 of them were
larger than anything the site draws, and go from 1,167 KiB to 249 KiB,
with the worst palette conversion at 3.8 RMSE against the full-colour
resize.
The name of the event and the name of the workshop were the same words
twice, on 42 of the 72 PyLadies BCN days, because Meetup announced each
day with the title of the one workshop it held: the page said "Selenium
for WebScraping" in its heading and again in its programme, and the row
of /events/ and the row of /talks/ said it twice as well. Those 42 are "PyLadies BCN Workshop,
<month> <year>" now, and the workshop keeps its own title on its own
page. The year is in the name because the month alone collides: six
Januaries, six Mays, five Junes.

canonical_photo is the one picture of a day, named in the front matter
of its event. No rule can pick it: the gallery of PyDay BCN 2016 holds
four pictures and three of them are a projector. heroBackground cannot
stand in for it either, because thirteen events share seven of those
images. Eleven events have a photo thread and all eleven now name one,
nine of them the group photo of the day. The two oldest take a picture
that lives loose in images/photos/, where the old site kept the few
somebody chose by hand, and the one of 2016 is the full lecture hall
picked out of a post of @PyLadiesBCN.

An edition says how many people came when a source says it in words.
PyDay BCN 2025 is the one page with a figure, 140, from the post that
closes the thread of the day. It is not a registration: AGENTS.md keeps
the two words apart, and an edition sells its tickets elsewhere.

The minutes of the eight general assemblies have a section of their own
on the page of the association, with the photograph of the day beside
each. Five of the eight have one. The minutes are the record the law
asks for; the photograph is the only thing that says what the day looked
like, because an assembly has no event page and no gallery. Stored as
WebP at 600px: 674 KiB of JPEG become 123 KiB.

"Hands-on Django with PyLadiesBCN (2022)" carries its group, so it stops
taking the "Other event" glyph on every listing.
/talks/ and /people/ hold 607 and 371 rows, and the only thing above
them was a legend that counted what was there. The legend is a filter
now, one sub-page per type and per role, with no JavaScript: the pages
come from a content adapter, the rows from the one cached walk the
listings already do, and the tab of the page you are on says so with
aria-current. 398 talks, 172 workshops, 27 lightning talks, 7 keynotes,
and 3 panels; the board, the committee, 23 organizers, 356 speakers, 10
mentors, 8 collaborators, and 1 volunteer. The role tabs read biggest
first, because the bar is a way into the list and not a hierarchy of the
association.

/people/role/assistants/ answers the question nobody had asked out
loud: the association never recorded who came to an event, and the page
says so. The archive holds the people a programme, a board page, or a
credit names, and an attendee is on no such list.

/organizers/ keeps its grid of faces, with the photograph in a circle
and in colour, and the modal behind each face is gone: it repeated the
page of the person, which is the one place that holds the detail. That
was an open defect since 2026-10-06.

Every role carries its glyph on /people/, as it already did on the page
of a person. The event cell of /talks/ carries the emoji of its event,
the one /events/ draws. The trail of a listing names its parent
inside the h1, as in "People:" and then the name of the person, with
the colon and no underline until the pointer is on it. A filter page prints no trail, so "Talks: Talks" cannot
happen, and a title that ends in a parenthesis takes no second one.

The venue of a row has no hover: it carried the string of the source
whenever it said more than the short name, and what it said was the
noise the short name exists to remove. The caption under each listing is
gone as well, on every one of them: it said the order and the grouping,
which the first columns say by themselves.

The schedule of an event reads on a phone, which it did not: the grid
was written into the page body by the template and could not reflow.
The gallery stays inside the screen between 992px and 1,200px. The
reading column follows the window instead of stepping out at every
Bootstrap breakpoint.
One of five proposals, with its header and its footer taken for the
whole site: a menu that changes between the home page and the rest is
worse than either of them.

The home page says what this is in one screen and where the next event
is: the name and the year it started, the four counts of the archive as
the way in, the video and the paragraph that say what PyBCN is, the two
Meetup groups, the ten most recent events, the photograph of each
event, the faces of the board and the committee, the four ways to take
part, and the communities next door. Every number is computed, and the
photographs are the canonical_photo of each event.

The bar is four groups in title case, The Association, PyLadies BCN,
Meetups, and Archive, with the search box and without Contact, which
is in the footer and says itself once. It replaces the menu of
config.toml, which read in the order of the file and shouted every item
in capitals. The footer is four columns, the pages that Take part also
links nested under the page they belong to, the seven accounts on one
line, and the licence of the code and of the content.

The hero is gone. It was a band a third of the screen tall with a photograph behind it, and the
photograph said nothing about the page it stood over: thirteen events
shared seven of them, and the association pages borrowed one from a
PyDay. What is left is the h1 itself, in the column, with everything it
carried: the search attributes, the trail of its listing, the total of a
listing, and the lang of a title that is not in English. The home page
draws its own, and says so with no_page_heading.

Verified on a build with drafts: no page carries the old hero, every
kind of page has exactly one h1, the footer draws its seven accounts,
bin/check-rendered passes, and bin/check-content is unchanged at 0
errors and 81 warnings.

The five proposals that led here, /option-1/ to /option-5/, are not in
this branch. They were the way to decide, not pages to publish, and the
site of a non-profit does not ship the sketches: the five layouts, the
five stylesheets, their bars and their footers, and the five entries
each needed in the allowlist of bin/check-rendered all go. The one
thing of theirs that stays is what the home page uses: the marks of the
communities under Our friends, and partials/friends.html, which holds
the five of them.
The title of an event is printed in the row of /events/, in the row of
/talks/, in the heading of its own page, in the search result, and in
the card a shared link draws. Two of them carried an emoji of their
own: a globe and a rocket around the Global Day of Coderetreat, and a
snake and a crab after the March meetup of 2024.

The row of a listing already marks every event with the glyph of its
type, so the title emoji is a second mark that says something else, and
the heading of the page reads as a post rather than as the name of a
day. Two titles carried one, the coderetreat and the code katas, and
both go.

The announcements on Meetup keep their emoji: this is the name the
archive gives the day, as with the 42 PyLadies workshops.
The page of a meetup, and of an edition with no grid, printed the
speakers of each talk as plain text. It was the one listing that
credits a talk to a person and did not take a reader to them: /talks/
links every name, the page of a person links every talk, and the day
itself was a dead end.

Each name is now the link of partials/person_page_link.html, which is
the one place that decides where a name goes, and the line opens with
the glyph of the Speaker role, the one /people/ prints before the same
word. A credit that is not a person, an id with no record behind it,
stays text, as it does everywhere else.

161 pages of the build gain the link.
The band is gone, and with it the partial that cropped a photograph to
its shape (partials/image_hero.html), the partial that drew it
(partials/hero-image-setheight.html, which nothing called any more),
and the stylesheet of the two (hero.scss, 54 lines) with its import.
Verified on a build: no page carries hero-image.

Four photographs go with them, 455 KiB: canodrom_header.jpg was the
first slide of the old carousel, meetups.jpg and practice-sessions.jpg
were the background of the meetup blocks of the old home page, and
news_bkg.jpg was behind its news band. Nothing reads them now, in the
content, the templates, the data, or the stylesheets.

heroBackground stays in the front matter of 38 pages, and so do the
photographs it names: partials/social_image.html reads it for the card
a shared link draws when the event has no gallery photo of its own. The
field no longer draws anything on the page.
The home page was rebuilt this week and became the best looking page of
the site, which left every other page reading as the one before it. The
pages of the archive now share its vocabulary, and the vocabulary lives
in main.scss where any page can reach it.

What is shared: the colours the home page introduced ($rule-grey,
$text-grey, $ink, $paper, $hover-blue, $blue-deep), one card radius,
one gap between blocks of 3rem, one gap under a title of 1.5rem; a
section title, the h2 at 27px with its rule under it; a sub title, the
h3 at 1.2rem; a card, with its border, its hover, and its focus; and a
frame for the boxes that are not links, the facts box and the figures.

What the pages gained and lost. The edition page loses six blue h2
bands, three runs of <br/>, the rule over the gallery, the grey band
behind the sponsors, and a padding trick that scroll-margin does
better; it gains the section titles and the rhythm. The sponsor card
and the person card are the card of the home page. The letters of
/people/ are section titles. /search/ takes the greys and the blocks.
The headings a markdown body writes take the scale of the site, which
is what the page of the association reads by. The listings keep their
table and gain the shared pills and the muted voice.

The muted grey is one value everywhere, #51616e, which is 6.39:1 on
white: the intros of the four listings were body black and the search
status was #555.

A long person name no longer pushes /people/ past the edge of a 390px
screen, which it did before this pass.

Three things the pass had changed and this commit keeps as they were
asked for: the organizers page is its grid of faces and not the
horizontal card, a level says how many people sit on its line, and a
sponsor card lifts under the pointer as well as taking the shared
border. On a phone the organizer cards go two to a line, because one
gave a 360px face and a board four screens long.

bin/check-rendered passes, bin/check-content is unchanged at 0 errors
and 81 warnings, the build holds its 1,377 pages, and none of the ten
pages checked overflows at 390, 768, or 1440.
The archive held 235 photographs, all of them from the threads of the
eleven large editions. The association has posted far more, and the rest
were sitting in the X export with nobody reading them: the importer
swept the day of the event, so a thread that ran past midnight or a post
of the morning after was out.

What places a photograph is the text of its thread first and the day
second. Reading it that way took several passes, and each failure is
worth writing down:

The account replies to its own posts of years before, so following the
chain of replies built threads that ran from 2014 to 2021. The chain is
cut at four days from the photograph.

A talk title of two or three generic words matches any post that uses
them, so a title has to be at least twenty characters before it can
attribute a photograph. Five days of 2014 and 2015 have programmes
written in one word (Ansible, Fabric, OpenStack, Spark + ipython, Api By
Example, Python for porn); those are safe when the day decides as well,
which is eight photographs more and the DevOps meetup of 2014 had none
at all.

A title that repeats across years says what the event is and not which
one, so the day decides between them.

Asking the text to name a talk is right for a post that names one, and
blind to the photograph of a full room whose post says "About to start
today's meetup!". There are a lot of those, so the day carries them: a
post of the association, with a photograph, published on the day of an
event, is a photograph of that event. Two posts of 2019-07-22 name the
two talks of the meetup of the 18th and the thread went out four days
later, so a reading places what no day could.

The last group the day got wrong needed a person: 21 posts of 2019-11-21
came out under the November meetup, and 20 of them are the thank-you
thread of PyDay BCN 2019, held on the 16th, one logo card per sponsor. A
logo card is not a photograph of a day, and the sponsors of that edition
are already on its page, so none of the twenty is imported. The thread
does confirm the sponsor list: the 19 accounts it thanks are the 19
sponsors the page carries, at the same five levels, and that list came
off the frozen site, so the thread is a second source for it.

What stays out: 30 posts that thank a sponsor one logo card at a time,
44 with no event to attach to (a call for proposals, a change of date, a
milestone, a meme, the announcement of a new logo, the events of other
associations), and 4 that fall on the day of an event and say nothing
about it (a box of stickers, a screenshot of a chat, a call to a
practice session of the week after, and the sign with the wifi
credentials of PyDay BCN 2019).

The galleries go from 235 photographs on 11 pages to 530 on 84, and 257
of them name the talk they show, because the text of the post is
"Ansible, by Ignasi Fosch" and the page of that day holds a talk by that
name. One photograph per post, which is the rule the galleries already
followed. Two carry a description instead of a quotation, because their
post had no text at all and a caption is the alt text of its image: they
are the only two captions on the site that the association did not
write.

They are stored as WebP: 23.2 MiB of JPEG from the export become 13.6
MiB, 41.5 per cent less.

bin/check-content gains one rule: an edition with a gallery and no
canonical photo is still worth saying, because the carousel of the home
page shows the editions; a monthly meetup with a gallery is not, and 64
of them have one now.

bin/import-x-photos takes --ids, a file of post ids, and the day stops
deciding when it is given. It reads one account, the one of the
association: a member who posts a photograph of a meetup from their own
account keeps it, so a personal archive is never opened, not even for
the day of an event. A t.co link that the export expands to
http://pybcn.org is written as https, because the site publishes no
insecure link; that is the only thing the importer changes about a
quotation. AGENTS.md records the rules, so the next import does not
learn them again.

What is left for a person to read: the 44 posts above, which name no
event at all.
The repository carried 8,460,730 bytes of PNG. Most of it was not a
choice: a photograph in a lossless container, and three files no page
has ever asked for.

Deleted, 207,500 bytes, because nothing names them: a template, a
stylesheet, the front matter of a page, nor the built site.
backgrounds/zen_of_python.png is what the hero band left behind.
static/favicon.png is byte for byte the favicon of the theme, and
head.html asks for favicon.ico by name and builds the 32 and the 180
pixel icons from the asset, so nothing links the copy at the root and
the site has no web manifest that could. backgrounds/logo_mini_white.png
has no reference anywhere. Two more go the same way, sponsors/apsl.png
and sponsors/verse.png, whose records point at apsl.svg and verse.jpg.

Sixteen person photographs were PNG, which is the wrong container for a
photograph: 4,669,887 bytes for images mostly under 600 pixels, and
773,727 of them for one 800 by 800 portrait. At quality 90 the same
sixteen are 695,224, which is 85.1 per cent less. Quality 90 and not the
82 the galleries use, because the browser is served a resize of this
file and not the file, and a lower quality would stack two lossy passes
on one image. Twelve of the sixteen are RGB with no alpha at all; of the
three that carry one, two have real transparency, which WebP keeps.

The 65 sponsor logos, the four marks of the friends of the association,
the placeholder of a person with no photograph, and one photograph of
PyDay BCN 2021 that was a PNG among 441 WebP: 1,201,435 bytes become
702,638. The logos go to lossless WebP and not to a lossy one, because a
logo is flat colour with sharp edges, which is what lossy compression
damages first, and the saving over lossless is four points.

None of that changes a page. The site never served those files: the
templates resize them and the pipeline already wrote WebP, so
/images/people/ holds the same 507 WebP and 124 JPEG as before, and
/images/sponsors/ the same 136 WebP and 5 SVG.

One change does reach the browser. The marks of the two Meetup groups
sit under static/, so the file goes out as it is on disk: 134,314 bytes
of PNG for two marks the stylesheet draws 88 pixels wide. As WebP at
quality 82 they are 10,348, and the downscale swallows the compression.
Rendered side by side in Chromium at 1440 pixels, the mark of PyBCN
differs from the PNG by 0.25 of 255 per channel on average and 3 at its
worst pixel, and the mark of PyLadies BCN by 0.64 and 5.

The PNG of the repository is 2,150,520 bytes now, and 1,855,481 of those
are the frozen snapshots under static/archives/, which keep their files
as they were served.
Comments across the templates, the data, and one prompt file named who
asked for a decision and on which day. The reason is the half worth
reading: a square box drawn around a circle is the worst of the two
shapes, whoever said so first. At that density a name stops being
attribution and starts reading as a warning not to touch, which is the
opposite of what the repository of an association run by volunteers
wants. The reasons stay, word for word; the names and the dates of who
asked go.

bin/prompts/extract-talks.md pointed at a note in a private notebook for
the measurement behind the model it names. It points at
tests/extraction/ instead, which is in this repository and is what
bin/check-extraction --selftest reads.

The names that stay are the ones that are data and not attribution: the
speakers the archive credits, the person who sits in two groups of the
organizers page, and the names the comments of the importer use as
their examples of an accent, a hyphen, or a pair of initials.
@DZPM
DZPM requested a review from a team as a code owner October 18, 2026 12:31
@DZPM DZPM self-assigned this Oct 18, 2026
@DZPM
DZPM requested review from ber2, mesejo and mrswats October 18, 2026 12:34
@DZPM DZPM mentioned this pull request Oct 18, 2026
30 tasks done
</div>
</div>
{{ else }}
{{- /* The search. On a page the site search indexes, the h1 is part of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Count hidden when title of the section ends in ) (For example Former Management Board (2018 - 2025))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That one is on purpose. The count is dropped when the title already ends in a parenthesis, so that "Former Management Board (2018 - 2025)" does not render as "Former Management Board (2018 - 2025) (7)", and so a filter page cannot read "Talks: Talks (398)". The cost is the one you spotted: those two board pages are the only role pages with no count.

If you would rather have the number everywhere, the alternative is to take the year range out of the title and let the heading print it, which changes the name of the group on the page. Happy to do that instead if you prefer it.

Comment thread bin/new-meetup Outdated
if not path.is_file():
return None
names = set()
for quoted, bare in re.findall(r'^(?:"([^"]+)"|([^"\s#][^:]*?))\s*:', path.read_text(encoding="utf-8"), re.M):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps here is better to use yaml.safe_load as in other places

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 244f482. It gives the same 87 keys today, so nothing changes in the output, but you are right that it is the wrong tool: a key with a colon in it, or a quoted value over two lines, would defeat the expression silently, and this function only ever reports a venue it does not recognise.

{{- $alt = $social.alt | default site.Title -}}
{{- else -}}
{{- with resources.Get "images/logo.png" -}}
{{- $image = (.Fill "1200x630 png Center").Permalink -}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

og:image crops logo. Fill "1200x630 Center" on 920x273 source cuts ~43% width. Fallback card reads "python barcelo". Hits every page without own picture (search, most person pages). Use Fit on canvas or prepared card asset.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in accaa39, and you were right that it reaches almost everything: 715 of the 1,307 pages shared that card, which is /search/, every listing, the association pages, and every person with no photograph.

It was worse than the crop as well. The source is transparent, so what was left of the logo sat on whatever colour the client painted behind it, black in Chromium.

The fix is a file, images/social-card.png, and not a Fit on a canvas: no crop or fit of a 3.4:1 logo fills a 1.9:1 frame without either losing something or leaving the frame empty, and a card is artwork, so it is reviewed by looking at it. The logo is scaled to 960x285 and centred on white with a 120px margin. A page that names its own picture is untouched: 468 event pages still share a photograph of the day and 124 person pages the portrait.

DZPM added 2 commits October 18, 2026 22:15
known_venues() found the keys of a YAML file with a regular expression,
which every other reader of that file does with yaml.safe_load. The
expression gets the same 87 keys today, and it is the wrong tool: a key
that carries a colon, a quoted value over two lines, or a nested map
would each defeat it in a different way, and silently, because the
function only reports a venue it does not know.
A page with no picture of its own shared its logo through .Fill
"1200x630", and Fill crops to the frame: the logo is 920x273, which is
3.4 times wider than it is tall, and the frame is 1.9, so 43 per cent of
the width went. The card read "python barcelo", cut at both ends, on the
transparent background of the source, which each client painted its own
colour, black in Chromium. 715 of the 1,307 pages of the site shared
that: /search/, every listing, the association pages, and every person
with no photograph.

images/social-card.png is the whole logo, scaled to 960x285 and centred
on white with a 120 pixel margin, at the 1200x630 the card wants. It is
a file and not a crop because no crop of a 3.4:1 logo fits a 1.9:1 frame
without losing something, and because a card is artwork: it is reviewed
by looking at it, which a generated crop is not.

A page that names its own picture is unchanged: 468 event pages share a
photograph of the day, and 124 person pages share the portrait.
@DZPM
DZPM requested a review from mesejo October 18, 2026 20:22
AUTHORS names the eighteen people who have written or reviewed here, in
the order they arrived, with the years, the account they worked from,
and their page on the site. Reviewing counts: two of the eighteen have
done nothing else.

bin/authors writes it from the commits, the pull request reviews, and
the person records, with no email anywhere. bin/authors --check writes
it again and compares, and runs in CI.

Four people with a page on the site now carry the GitHub account they
worked from.

humans.txt counts from the archive and not from the Meetup group: 281
events, 607 talks and workshops, 370 people, 81 sponsors, 87 venues, and
530 photographs. llms.txt gains the talks and the search.

The two headers that document the card of a shared link say what the
code does again.
@DZPM
DZPM force-pushed the work/phase-4-final branch from e4bcdd2 to baa051c Compare October 19, 2026 13:09
@DZPM
DZPM merged commit c34b9fe into edition Oct 19, 2026
7 checks passed
@DZPM
DZPM deleted the work/phase-4-final branch October 19, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants