What creaks, with evidence
Every number below was computed from catalogue.json, stats/data.json, spec/curated.js, mappa/sites.js and index.html as of this merge. Nothing here is an impression.
The five buckets predate the collection.
work has 8 entries; games has 84. data and tools are catch-alls: when the spec's ten families are laid over the catalogue's top-level tools entries, they fan out into eight different families. The buckets are not wrong so much as no longer a partition of anything.
| bucket | entries | top-level | |
|---|---|---|---|
| games | 84 | 20 | |
| bluesky | 75 | 26 | |
| tools | 66 | 36 | |
| data | 63 | 26 | |
| work | 8 | 4 |
tools → spec families: platform 10 · science 8 · generative 5 · reference 4 · work 3 · reading 3 · games 2 · math 1
data → oneill 5 · science 5 · math 5 · generative 4 · platform 3 · work 2 · social 1 · games 1
Three taxonomies disagree, and each map trusts a different one.
The landing uses c (5 buckets). The spec uses families in spec/curated.js (10, keyed by surface). Mappa and the orrery use WINGS plus a hand-kept ASSIGN table in build-mappa.mjs (9, keyed by name). Nothing checks them against each other, so a site can be a “tool”, a “science” surface and a citizen of “The Workbench” at once, and the reader is asked to hold all three.
spec.families platform social work science oneill generative games reference reading math
mappa.WINGS social lenses bench cards studio arcade sandbox math reading
bluesky → wings: lenses 19 · social 6 · studio 1
tools → wings: bench 29 · studio 4 · reading 2 · arcade 1
“bench” absorbs 55 of 112 top-level sites
k (weight) is fiction for half the catalogue.
137 of 296 entries carry k: 1. The comment in index.html still calls it “commits”, the catalogue calls it “weight”, and it drives orrery ring placement and sort order. Against real per-surface commit counts from stats/data.json the correlation is 0.20. Five surfaces with more than fifty commits sit at k: 1.
| surface | k | git commits |
|---|---|---|
| lab | 1 | 349 |
| loop | 1 | 338 |
| bisk | 1 | 158 |
| mappa | 1 | 84 |
| plant | 1 | 79 |
| poll | 75 | — |
k distribution: 1 → 137 · 2–4 → 58 · 5–9 → 42 · 10–19 → 33 · 20–49 → 22 · 50+ → 3
corr(k, commits) over 91 top-level = 0.20
The heat signal has gone out.
t (last touched) is hand-maintained and was last refreshed in bulk in May–June. The page recomputes a from t at load: hot within 7 days, warm within 14, cold after. Today that makes 270 of 296 entries cold, so the legend gradient, the orrery dial and the timeline's right edge all read as one colour. Meanwhile git knows the real answer: every one of the 248 entries whose surface appears in the stats has a newer commit than its t.
| entry | catalogue t | git last |
|---|---|---|
| poll | 2026-05-30 | 2026-09-07 |
| photo | 2026-05-30 | 2026-09-07 |
| bisk | 2026-06-02 | 2026-09-06 |
| disk | 2026-05-29 | 2026-09-07 |
| airchat | 2026-05-29 | 2026-07-25 |
stale t: 248 / 248 · live tiers today: hot 7 · warm 4 · cold 270 · undated 15
Names are the primary key, and names collide.
p (parent) points at a display name. Seven names appear more than once, arena three times. Any child of a duplicated name is ambiguous today; any future rename silently orphans children. The registry already has a stable key (surface); the catalogue needs one of its own.
org rite.mino.mobi/org/ · org.mino.mobi
arena hoop…/v099/arena/ · os…/arena/race-01/ · fluoddity…/arena.html
voronoi hoop.mino.mobi/paint/ · mino.mobi/voronoi/
orb photo…/orb/ · tjs…/orb/
ink mino.mobi/wars/ink/ · ink.mino.mobi
sprite biome…/sprite/ · mega…/sprite/
The hierarchy is two levels deep and lopsided, and the maps ignore it.
184 of 296 entries are children, but only four are grandchildren. g has 24 children, geometry 19, rite, read and fluoddity 13 each. The orrery draws only the 112 top-levels; the constellation, subway, force and periodic views draw all 296 as peers. Neither is the shape of the collection, which is roughly 50 hubs with satellites.
| parent | children | |
|---|---|---|
| g | 24 | |
| geometry | 19 | |
| rite | 13 | |
| read | 13 | |
| fluoddity | 13 | |
| photo | 12 | |
| atmosphere | 11 | |
| hoop · tjs | 10 |
depth 0: 112 · depth 1: 180 · depth 2: 4 · hubs (top-level with children or k≥10): 49
Prose covers 167 of 296. Search only knows those 167.
The curated <li> list in index.html has 167 descriptions; 130 catalogue entries (every child of rite, read, g…) have none. functions/search.js is generated from the li list, so the ask panel cannot find anything without a description, which is most of the sub-sites.
li not in catalogue: 1 (fortress)
search catalogue source: index.html li → not catalogue.json
tags in li: canvas 48 · atproto 32 · math 20 · wasm 14 · webgpu 12 · webgl 10 · react 9 · embeddings 9 · welsh 7 · rust 7 …
Five entries sit in the wrong section, and the section list is by index.
sections[] records section boundaries as entry indexes (“work starts at 74”). Insert an entry and every later boundary is off by one. Five entries already fall in a section that is not their category.
79 farm-next games, listed under work
169 fifty bluesky, listed under tools
277 canvas tools, listed under games
278 pizza tools, listed under games
The backlog is one-third phantom.
catalogue-coverage.mjs keys on directories; the catalogue keys on URLs. The clock/ dir ships as g.mino.mobi, so 24 of the 28 clock/* “pending” sub-sites are already catalogued under g.mino.mobi/…. Forty of the 80 pending leaf names already exist as catalogue names. The true backlog is closer to 40 than 80.
pending leaf name already a catalogue name: 40
registry dir ≠ host (20): geometry→math · draw→canvas · clock→torus · answers→ask · bakery→bake · empathy→empath · labglass→glass · pokemon→poke · finance→fin · wars→war …
One 250 KB file does everything.
index.html is 4,523 lines: 44 KB of var P, 76 KB of curated list, 106 KB of script across seven renderers plus git history plots, and a hand-written mobile fallback of about 150 unlabelled links. Every visitor downloads all of it to see one view. The site-count says “125 surfaces” in HTML and is overwritten to “296 surfaces” by script, while the registry says 102 surfaces. The word surface now means two things.
var P 43,875 · li list 76,201 · style 21,303 · script 106,509 (5 blocks)
vis modes: constellation subway periodic force depends orrery timeline
registry surfaces 102 · catalogue “surfaces” 296 · HTML literal “125”
Every entry, placed
The structural ideas further down only matter if the entries themselves are right, and most of the mess is path dependence: a thing lives where it was born, not where it belongs. This pass reads all 296 entries plus the 80 in the coverage backlog and gives each one an action. It is hand-written in rethink/proposal.json, and scripts/build-rethink.mjs refuses to build this page unless every catalogue entry is placed exactly once. Edit the file, rebuild, argue with it.
- catalogue entries296
- buckets5
- hubs the maps could drawnone declared
- entries with no description130
- hubs · standalone sites32 · 51
- entries, after merges and new listings334
- merged or retired13
- listed for the first time51
The entry count goes up, because the backlog is real: fable alone has eight sub-sites nobody listed. What goes down is what the front door has to show. The hubs are the whole landing page; the standalone sites sort by kind under them. One hub is pinned: procgen, two dozen generators that were scattered through games, tools and data, now one category with its own door at /procgen/.
keep unchanged · fold was top-level, becomes a page inside a hub · regroup a page moved to a different hub · promote a page becomes its own site · new listed for the first time · merge an iteration or duplicate retired into its sibling · retire frozen versions and scaffolding leave the catalogue · decide two things claim the same slot; a person picks · look nobody has written down what this is, so its placement is a guess. A pinned hub is a category the landing shows first, with its own door.
Decide
Two things claim one slot. Nothing downstream can pick for you.
Look
No description anywhere in the repo, so the placement above is a guess. This is the editorial backlog, and it is the real one: entries.
A plan for all 630
The catalogue lists 296 things. The apex serves 630 directories with an index.html. The gap is mostly scratch: hoop's version directories, chapters and exhibits inside listed sites, and source trees the root worker uploads because it uploads everything. “Reachable from the landing” has to mean something for every one of them, so every endpoint gets a fate, computed from the proposal's tree plus the coverage rules in catalogue.json:
The rule for content is the one that makes the plan honest: a page inside a hub is reachable only if that hub's door links it. Today most doors are the surfaces' own index pages and they mostly do. The exceptions are the look rows, where the door itself is undescribed, so the content beneath it is reachable in theory only. Internal endpoints are not a navigation problem but a serving problem: they should leave the upload, via .assetsignore or a carve-out, and the figure keeps drawing them until they do.
Dead, as of the last probe
Today vs. proposed, same data
Both panels lay out the real catalogue with the same deterministic spiral, coloured by today's five buckets. Left: every entry as a peer, which is what the constellation, force and periodic views do now. Right: only the hubs from the content pass below, sized by how many entries each one carries. Hover either for names.
Ideas, in three lanes
Cost is a gut estimate: S an afternoon, M a session or two, L a week of sessions. The outlined cards are the ones the rest depend on. Chips name the finding each idea retires.
Lane 1 · The data model
Give every entry a stable id
SAdd id (host-and-path slug: rite/atlas, g/antoine) and make p point at ids. n stays the display name and may repeat. A one-shot migration script writes the ids; preflight refuses a duplicate id or a dangling parent.
Replace the bucket with facets
MRetire c. Add three orthogonal fields: kind (one of: app · toy · explainer · reading · engine · infra), domain (bluesky · oneill · math · welsh · science · finance · …, one or more), and stack (the tags already on the li: canvas, atproto, wasm, webgpu…). The landing sections become “group by kind”; the maps colour by domain; search filters by stack.
One taxonomy, three projections
MFold spec families and mappa WINGS/ASSIGN into the catalogue as facet values, then delete the hand tables in spec/curated.js and build-mappa.mjs. The spec reads kind+domain; mappa reads domain as its countries. A site can no longer belong to three unrelated places.
Derive heat and weight from git
Sbuild-git-stats.mjs already computes per-surface last and total at fetch-depth: 0 in CI. Let gen-landing-catalogue fill t and k from stats/data.json and treat hand values as overrides only. The 248 stale dates and 137 fake weights disappear on the next preflight --fix.
Prose lives in the catalogue
MMove the 167 curated descriptions into a d field (HTML allowed) and generate the <li> list and functions/search.js from it. Then the 130 undescribed entries have an obvious empty slot, coverage can count it, and search finally sees the whole collection.
Tiers instead of a flat array
SAdd tier: hub (a door: has children or is a whole domain), site (a standalone thing), page (content inside a hub). Derived by default, overridable. Every map renders hubs first and expands the rest on demand.
Sections by facet, not by index
SDelete sections[]. The generator groups by kind (or whatever facet the landing chooses) and orders within a group by derived weight. Inserting an entry can no longer shift a boundary.
Coverage keys on URLs
STeach catalogue-coverage.mjs to map a directory to its live URL through the registry (dir → endpoint, plus the paths a surface stages), and compare URLs to URLs. The 24 phantom clock/* rows vanish and the real backlog of about 40 stands out.
Lane 2 · The front door
A shelf of hubs, not a sky of dots
MAbove the fold: the ~50 hubs as a compact shelf grouped by kind, each with its one-line description, its satellite count and a real last-touched date. That is the whole first screen. Nothing with tier: page appears until a hub is opened.
A “this week” strip from git
SReplace the hot/warm/cold legend with a short list of what actually changed in the last seven days, straight from stats/data.json. When nothing changed, it says so. It is the one part of the page that is honest about time without anyone editing a date.
Instant filter before the model
SThe ask panel calls a 70B model for every query. Put a client-side fuzzy filter over id, name, facets and d in front of it: typing “welsh” or “webgpu” narrows the shelf immediately. The model stays for questions, not for lookups.
Split the 250 KB file
MGenerate catalogue.js (or fetch catalogue.json) instead of inlining var P; move each renderer to landing/<mode>.js loaded when its tab is first opened; keep the generated li list as the no-JS fallback. First paint drops to roughly 40 KB and the generators stop rewriting a 4,500-line file.
Generated hub pages
MFor every hub without its own index (rite, read, g, photo, geometry…), generate a small page from the catalogue listing its children with descriptions. The hierarchy gets doors of its own; the landing links to the door instead of every room.
Retire the hand-written mobile fallback
SThe .orrery-fallback nav is 150 links in two unlabelled groups, maintained by hand. Once the li list is generated by hub, it is the fallback. Delete the nav and the HTML literal “125 surfaces”.
One word for “surface”
SThe registry has 102 surfaces (things that deploy). The landing counts 296 “surfaces” (things you can visit). Call catalogue entries sites everywhere on the page and in the generators; keep surface for the registry. Ninety seconds of confusion per new reader, retired.
Lane 3 · The maps
Level of detail everywhere
MOne rule for all seven renderers: draw tier: hub nodes; draw a hub's children only when it is hovered, focused or zoomed. The maps stop being 296-node hairballs without any renderer changing its geometry. The demo above is this rule applied to a spiral.
Seven modes answer three questions
SConstellation, force and subway draw the same graph three ways. Keep one view per question: what is here (the shelf or a treemap), what is alive (the timeline), how it is wired (depends, from the registry's uses). Retire the other four; each is a renderer nobody has to maintain.
A treemap that scales past 300
MArea by derived weight, nested by kind → hub → children, coloured by domain. It is the only layout in which 600 leaves stay legible, because small things become small rectangles instead of overlapping labels. It is the honest version of “periodic”.
Orrery rings by tier
SInner ring hubs, middle ring standalone sites, outer ring the long tail, instead of splitting by a weight that is 1 for half the entries. Ring speed already encodes nothing; let radius encode something.
Mappa: cities are hubs, districts are children
MThe atlas currently plants 296 cities. Plant the ~50 hubs as cities sized by satellites, draw children as districts inside the city marker at zoom, and let domain be the countries so the wings table can be deleted.
Timeline of births, not touches
Sb (birth) is the one date the catalogue gets right, because it never changes. A timeline by birth month, with hubs as anchors and children as ticks under them, shows the story of the collection: 26 in March, 91 in June. Last-touched belongs to the “this week” strip.
A schema to grow into
The same short keys, so nothing downstream breaks on day one. Green is added, struck is removed, the rest stays. Every field marked derived is written by preflight --fix and may be overridden by hand.
Migration is mechanical: ids from URLs, kind from a first pass over c plus the spec families, domain from the mappa wings plus the li tags, tier from children counts, d from the li list keyed by URL. A script can propose all of it and a person corrects perhaps forty rows.
Every catalogue entry, flagged
All 296 rows from catalogue.json, joined to stats/data.json by surface. “Live” is the tier the landing page computes today from t. Flags: stale git has a newer commit than t · nodesc no curated description · dup name appears more than once · weight k≤1 on a surface with more than fifty commits · nodate no t · dead the last probe got a 4xx/5xx, a timeout, or the apex landing page instead of the page.
| entry | bucket | proposed hub | action | k | git | t | git last | live | kids | flags |
|---|
An order of work
Numbered because the order matters: each step is safe on its own, ships through preflight --fix, and makes the next one smaller. Nothing here touches a deploy until step six.
Ids and parents
Migration script adds
idto every entry and rewritespto ids. Preflight gains two checks: unique ids, no dangling parents. Every generator keeps working becausenanduare untouched.gate: preflight green · var P byte-identical apart from the new fieldHeat and weight from git
gen-landing-cataloguereadsstats/data.jsonand fillst/k; hand values become overrides. The landing's legend lights up again without anyone editing a date.gate: 0 stale rows in the audit table · CI at fetch-depth 0 already provides the statsApply the content pass
A script reads
rethink/proposal.jsonand rewritescatalogue.json: parents moved, merges and retirements applied (retired URLs go intonotListedas content), backlog entries added withd: "". The seven decide rows are answered in the proposal first, by hand.gate: coverage backlog ≈ 0 · top-level ≈ 83 · every hub has a doorDescriptions into the catalogue
Move the 167 li descriptions to
d; generate the li list andsearch.jsfrom the catalogue. Then work the look list, hubs first: a sentence each is enough to place them properly.gate: nodesc = 0 for hubs · search finds a child of rite by nameFacets and the single taxonomy
Add
kind,domain,stack,tier; the proposal already carries kind and domain per hub. Deletesections[], specfamilies, mappaASSIGN. Coverage switches to URL keys.gate: one taxonomy file · every projection regenerates from itThe shelf and the split
New landing: shelf of hubs by kind, the “this week” strip, instant filter; renderers split into modules; li list becomes the no-JS fallback; the hand-written mobile nav goes. This is the deploy, on the root surface's owning branch.
gate: first paint under 50 KB · every hub reachable in one click · deploy log binds mino.mobi (custom domain)Maps get level of detail
Apply the hub-first rule to whichever renderers survive, add the treemap, re-ring the orrery by tier, replant mappa's cities. Each is its own small change against the same catalogue.
gate: no map draws more than ~60 labels at rest