Four hundred entries, and a way to find yours
Every project's own favicon, pulled from its own repo, now filters the feed by pseudomonorepo and points at the right changelog.
Four hundred entries, and a way to find yours
Why Care?
An aggregate changelog gets less useful the better it works. At forty entries you scroll. At 438 across 37 repositories you give up, because the thing you came to read is somewhere in a reverse-chronological wall of everyone else's work.
The tree already has a structure that solves this — it just wasn't on the page. Work here lives under a handful of pseudomonorepos, and each of those holds a dozen repos. That's two levels, and each wants a different affordance: you filter at the top level to narrow the feed, and you navigate at the bottom level to read one project's ledger end to end.
Both now exist, and both are wearing the right project's face.
What's New?
Filter the feed by pseudomonorepo. Four chips, plus All:
| Entries | |
| All | 438 |
| AI Labs | 228 |
| Astro Knots | 120 |
| Content Farm | 57 |
| Misc | 33 |
Misc is not a dumping ground — it's the standalone repos that never took a parent, and collecting them beats either dropping them or giving each its own chip. Four controls is a filter; eleven is a second navigation.
Every project's own favicon. Not generated monograms — the actual mark, read out of each project's own repo. 20 of 39 streams have one.
Every entry knows where it came from. The origin label on each row links to that project's changelog page, and so does its row in the ledger below. 39 per-repo changelog pages, each with its own strip, its own line-kind split, and its own entries.
How it Works
Getting the marks
There is no favicon convention in this tree, which is the whole difficulty.
Repos with a splash page keep the project mark at splash/public/favicon.svg.
Plain sites keep it at public/favicon.svg. A couple of nested apps keep it
deeper than either. So sync-icons.mjs walks an ordered candidate list and
falls back to a scan.
The scan needed a guard, and it needed one immediately. Its first run gave
hypernova-site a Water Foundation favicon — TWF trademarks were sitting in
its public/trademarks/ directory, left over from other work, and they were the
only SVGs present. Wrong brand is worse than no brand.
So an undecorated favicon.svg is taken as the project's own by definition, and
a decorated one has to prove it:
// `favicon__LearnStart--Dark.svg` names its project. The TWF files do not
// name hypernova. Tokens under four characters and the word "site" are
// dropped, or every site in the tree would match every other.
const tokens = `${stream.slug} ${stream.title}`
.toLowerCase().split(/[\s-]+/)
.filter((t) => t.length >= 4 && t !== "site");learnstart keeps its mark. hypernova correctly falls through to none.
No JavaScript
The filter is a radio group and :has(). Radio rather than checkbox because
these are views of one feed, not additive facets — single-select is what
pure-CSS isolation can express honestly, and a multi-select would need real
state.
.feed:has(#g-ai-labs:checked) .rows > :not([data-group="ai-labs"]) { display: none; }Every entry is already in the DOM, so narrowing is instant, survives a page refresh no worse than any static page, and costs nothing on a cold load.
The feed renders deeper than it shows
Thirty entries is the right unfiltered length. But filtering thirty down to Content Farm leaves three rows, which makes the filter feel broken rather than selective.
So each group contributes its 25 most recent, everything past the first
thirty overall is marked data-extra, and those rows are revealed only when a
filter is active. One rule, no duplication, no second render pass.
Under the Hood
Marks are designed for their own ground
The first version put a white plate behind every icon — a defensive habit,
assuming dark marks on a dark page would vanish. It was exactly wrong. ai-labs
and content-farm each ship their own background <rect>, and astro-knots is
transparent line-art stroked in indigo, chartreuse and amber. The plate was
covering a transparent mark with the one background it was never designed to sit
on, and rendering the Astro Knots knot as a white square.
No plate on any of them now. They were already correct.
A group is a tree root, not a taxonomy
groupOf() walks a stream's ancestors to its root and checks it against the
three named groups. Nothing is hand-assigned, so a stream re-parented in
streams.yaml moves to the right chip on the next build with no second list to
keep in sync — which is exactly what happened to lossless-site earlier today
when it moved from a standalone root into the Astro Knots subtree.
Chrome you have to fight is chrome you should not use
The filter started as a <fieldset> with a visually-hidden <legend>, which
renders a bordered box with an inset label until you override it per browser.
It's a <div role="group" aria-label="Filter by project"> now — same semantics
for a screen reader, nothing to override.
Files Touched
scripts/sync-icons.mjs— collect each project's favicon from its own reposcripts/lib/clones.mjs— repo-to-clone resolution by origin remote, shared with the line syncsrc/lib/groups.ts— the four buckets, derived from tree rootssrc/pages/index.astro— the chips and the CSS-only filtersrc/components/ProjectRow.astro— per-project marks in the ledgerpublic/icons/— 20 collected favicons
What's Next
There is no per-repo filter on the home feed. Repo-level is navigation only — you click through to
/stream/<slug>/. Filtering to one repo without leaving the front page is the obvious next rung.19 of 39 projects have no favicon at all.
fullstack-vcships onlyfavicon.afdesign, an Affinity source file, and needs an SVG export before it can appear anywhere.The share banners exist —
ogimage__AI-Labs--Banner.jpgand friends — but at 1200×630 they are the wrong shape for a chip. They would suit a group header or a hover card.Ledger row counts still flip meaning by depth: a root shows its whole subtree, a child shows only itself, so MemoPop AI reads 5 while its own child reads 71.
References
Companion to [[2026-08-18_01]], which covers the line-kind split and the daily commit series shipped alongside this.
