mino.mobi · landing page · rethink · 2026-09-07

The Catalogue Rethink

The landing page was designed for about a hundred sites and now carries 296 catalogue entries over 630 reachable endpoints, sorted into five buckets that were named before most of the sites existed. Its shape is the order things were built in: every session got a directory, every directory got an entry. This page is the audit of what creaked, a content pass over every entry saying what collapses, what splits and what moves, and an order to do it in.

630
reachable endpoints (every dir with an index.html); all placed below
296
catalogue entries · 112 top-level · 184 children
5
buckets in c · bluesky · work · data · tools · games
3
competing taxonomies: catalogue buckets, spec families, mappa wings
7
landing visualisations, all drawing the same flat array
270
entries the live page colours “cold” today · 7 hot · 4 warm
The whole space

Every reachable endpoint, one figure

This is the landing hero, prototyped at /next/: the entire site as one radial tree. The centre is the landing. The first ring is the seven wings the landing is organised by. The second is the hubs and standalone sites inside each wing, named along their arc where the arc is long enough. The third is the pages inside each hub. Everything beyond is content reached through those pages, down to hoop's frozen versions. Sector width is how many endpoints live under it. The faint outer band is what is reachable today but should not be: build output and source directories the apex serves by accident. Hover for the path, click to go.

hover a sector
Diagnosis

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.

bucketentriestop-level
games8420
bluesky7526
tools6636
data6326
work84

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.

catalogue.c bluesky work data tools games
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.

surfacekgit commits
lab1349
loop1338
bisk1158
mappa184
plant179
poll75—

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.

entrycatalogue tgit last
poll2026-05-302026-09-07
photo2026-05-302026-09-07
bisk2026-06-022026-09-06
disk2026-05-292026-09-07
airchat2026-05-292026-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.

atlas rite.mino.mobi/atlas/ · atlas.mino.mobi
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.

parentchildren
g24
geometry19
rite13
read13
fluoddity13
photo12
atmosphere11
hoop · tjs10

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 descriptions 167 · catalogue entries 296 · entries with no prose 130
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.

78 farm games, listed under work
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 80 · clock/* 28 → already listed as g/* 24
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.

index.html 250,374 B · 4,523 lines
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”
Content pass

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.

Today
112top-level
  • catalogue entries296
  • buckets5
  • hubs the maps could drawnone declared
  • entries with no description130
Proposed
83top-level
  • 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.

      Coverage

      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

        Live demonstration

        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.

        Today296 peers
        Proposed49 hubs · 247 tucked inside
        blueskyworkdatatoolsgamesring on a hub = child count
        Brainstorm

        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

        S

        Add 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.

        foundation
        name collisionsambiguous parents

        Replace the bucket with facets

        M

        Retire 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.

        foundation
        five bucketscatch-alls

        One taxonomy, three projections

        M

        Fold 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.

        foundation
        three taxonomies

        Derive heat and weight from git

        S

        build-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.

        stale tk is fictionheat legend

        Prose lives in the catalogue

        M

        Move 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.

        130 without prosesearch blind spots

        Tiers instead of a flat array

        S

        Add 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.

        lopsided hierarchycrowded maps

        Sections by facet, not by index

        S

        Delete 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.

        5 mis-sectioned

        Coverage keys on URLs

        S

        Teach 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.

        phantom backlog

        Lane 2 · The front door

        A shelf of hubs, not a sky of dots

        M

        Above 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.

        the pitch
        296 peersfirst screen

        A “this week” strip from git

        S

        Replace 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.

        dead heat signal

        Instant filter before the model

        S

        The 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.

        search costmobile nav

        Split the 250 KB file

        M

        Generate 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.

        250 KB indexgenerator churn

        Generated hub pages

        M

        For 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.

        184 children on the landing

        Retire the hand-written mobile fallback

        S

        The .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”.

        hand-kept lists

        One word for “surface”

        S

        The 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.

        vocabulary

        Lane 3 · The maps

        Level of detail everywhere

        M

        One 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.

        the pitch
        crowded maps

        Seven modes answer three questions

        S

        Constellation, 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.

        106 KB of script

        A treemap that scales past 300

        M

        Area 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”.

        label overlap

        Orrery rings by tier

        S

        Inner 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.

        k is fiction

        Mappa: cities are hubs, districts are children

        M

        The 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.

        three taxonomiescrowded atlas

        Timeline of births, not touches

        S

        b (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.

        stale t
        Sketch

        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.

        { "id": "rite/atlas", // stable key; p points here. host/path slug "n": "atlas", // display name, may repeat "u": "https://rite.mino.mobi/atlas/", "c": "bluesky", // gone: the bucket "kind": "app", // app · toy · explainer · reading · engine · infra — exactly one "domain": ["bluesky"], // one or more; colours the maps; mappa's countries "stack": ["atproto","canvas"], // the li tags, promoted; search filters on them "tier": "page", // hub · site · page — derived from p and children, overridable "p": "rite", // parent id, was parent name "d": "Where the …", // the curated description, moved out of index.html "k": 14, // derived: git commits on the surface; hand value = override "t": "2026-09-06", // derived: git last-touch "b": "2026-05-20", // birth; hand-kept, never changes "surface": "rite" // unchanged: FK into deploy-registry.json } "sections": [ { "before": 74, "label": "work" } … ] // gone: grouping is by kind "kinds": [ "app","toy","explainer","reading","engine","infra" ] // closed list; preflight enforces "domains": [ { "id":"oneill", "label":"The O'Neill cylinder", "hue":32 } … ] // the one taxonomy

        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.

        The audit

        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.

        entrybucketproposed hubactionkgittgit lastlivekidsflags

        Sequence

        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.

        1. Ids and parents

          Migration script adds id to every entry and rewrites p to ids. Preflight gains two checks: unique ids, no dangling parents. Every generator keeps working because n and u are untouched.

          gate: preflight green · var P byte-identical apart from the new field
        2. Heat and weight from git

          gen-landing-catalogue reads stats/data.json and fills t/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 stats
        3. Apply the content pass

          A script reads rethink/proposal.json and rewrites catalogue.json: parents moved, merges and retirements applied (retired URLs go into notListed as content), backlog entries added with d: "". The seven decide rows are answered in the proposal first, by hand.

          gate: coverage backlog ≈ 0 · top-level ≈ 83 · every hub has a door
        4. Descriptions into the catalogue

          Move the 167 li descriptions to d; generate the li list and search.js from 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 name
        5. Facets and the single taxonomy

          Add kind, domain, stack, tier; the proposal already carries kind and domain per hub. Delete sections[], spec families, mappa ASSIGN. Coverage switches to URL keys.

          gate: one taxonomy file · every projection regenerates from it
        6. The 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)
        7. 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