Skip to content

feat: taste-tested homepage, tracks ordered by measured demand - #136

Merged
harlan-zw merged 43 commits into
mainfrom
feat/alt-homepage
Sep 7, 2026
Merged

harlan-zw merged 43 commits into
mainfrom
feat/alt-homepage

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

Replaces the homepage with the taste-tested pivot, then follows the change through the registry and the pages around it.

Architecture across three lanes: the homepage and track grid in the browser, the clusters API, taxonomy and classifier in the Worker, and the backfill job writing to D1. The weekly email band is marked removed.

The track order used to come from the API, which sorts by how many skills a track holds. It comes from the page now, and the column count decides how many survive.

Data flow for ordering the tracks: the grid fetches every track, the API returns them sorted by supply, the page overrides with a demand order, and the grid slices to rows times columns.

The homepage

/alt becomes /. The hero states the category and hands the visitor two ways to move inside one screen: a real search typeahead, and a real install command prefilled with the top trending repo. Under it, the agents that command installs into.

Bands, in order: what devs are sharing this week, tracks for your kind of work, the taste test, watch what changes, a whole collection in one command, and a compact strip for maintainers who want to write a skill. Community came out of the top navigation.

The brand system repeats rather than decorating the hero alone: a dot-grid rule on every band edge, a rose underline on one phrase per heading, dot-grid marks on the door tiles and the taste-test numerals, a watermark word in the gutter, and a rose packet riding the trending rail.

Tracks, ordered by measured demand

/api/clusters sorted tracks by how many skills each holds, which is supply. The grid takes demand now, measured on 2026-09-04 from production: the 50 named skills trending over a week, the 50 over a month, and the top 50 repositories by stars, scored with a 1/log2(rank) decay.

48% of trending skills had no classifier category, so all 80 named skills were read and placed by hand. The measurement changed the taxonomy, not only its order:

  • diagrams is new. Diagrams and codebase maps took 10.8% of weighted demand and the top skill of the week, and their skills were scattering across three tracks that promise something else.
  • research returns 22 days after it was culled for having no demand. It now carries 6.5%, led by browser-use.
  • diagramming joined the classifier, and a targeted backfill moved 111 production skills onto it. 53 had no category at all before.

Nothing retired against the two additions, which breaks the file's subtraction principle on purpose: a page answers search demand and costs a URL, a tile answers trending demand and costs the scarcest space on the site. The grid caps at twelve, so Performance, Everyday coding and SEO keep their pages and lose their tiles.

Bugs found on the way

  • A keyboard trap on the old homepage. The weekly band's email preview set aria-hidden and a negative tabindex, which leaves every link inside the email in the tab order. Five controls were reachable and invisible. It is inert now.
  • Six duplicate ids on every skill page. The file tree renders twice and built folder ids from the path alone, so aria-controls pointed the second tree's buttons at the first tree's lists.
  • The published-skills feed could link to pages that do not exist. LEFT JOIN let an activity row outlive its skill, and a fallback invented a repo name for it.
  • The track row cap hid nine tracks on a phone and left them tabbable.
  • The lead collection card could print "Install all 0 skills" beside a working copy button.

Claims the data could not support

The digest promise said "what changed and why", but the AI summary is paused, so the email ships raw commit messages. "Stars are the only count we show" sat beside three other counters. "Owner verified" is an owner allowlist, not a per-skill check. Two buttons promised collections and opened a curators directory. Three links took a 301. All corrected.

Guards added

  • Production smoke asserts the hero's own words on /, covers both new track pages, and checks that every featured collection answers 200 at its own URL, because a dozen retired collection slugs are permanent redirects and nothing stopped one being featured again.
  • The hero's copy button records an install event. It is the page's primary call to action and its copies were invisible.
  • scripts/seed-trending-local.ts writes the social evidence the trending band needs, so the site's top call to action is no longer the one surface nobody can see while working on it.
  • GLOSSARY.md gains track. One concept was called track in the interface, cluster in the code and category in the database.

The brand, carried through the site

Three parts of the homepage's system travel to every page, and only three, so inner pages keep their own shape: the rose selection colour, band edges printed in the dot grid, and ledger numerals inked rose and printed in that same grid, on track pages and the trending board.

The trending board's ranks encode a documented decision, that the top three read first through contrast rather than type scale. That mechanism survives as opacity, so the emphasis is unchanged.

Vocabulary followed the new GLOSSARY entry. The curators page called itself Community, which the glossary bans as a proper noun, and counted creators, which it bans for a person. Two eyebrow labels sat above headings on the brand-kit page, which DESIGN.md bans and scripts/check-eyebrows.ts catches; that checker is clean now.

Verification

1587 unit tests, typecheck clean. Checked in the browser at 390, 768, 1024 and 1440, in both themes: no focus traps, no duplicate ids, no missing alt text or accessible names, no horizontal overflow. Reduced motion stops the packet and the rail's auto-scroll. Production counts were queried directly rather than inferred from the local database.

Method and numbers for the demand measurement: ~/scratch/notes/skilld-track-demand-2026-09-04.md.

https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ

Repeat the dot grid and the rose across every band, then polish:

- Band edges print in the dot grid over the border, at 18%.
- One phrase per heading carries a rose underline. Native text-decoration
  so it survives a line wrap.
- Watermarks fit inside the gutter whole, 3rem, widest screens only.
  A word cut off by the fade read as a rendering bug.
- Door marks are dot-grid tiles that light on hover.
- Taste-test numerals are printed in the grid.
- Tasted chip on the lead collection, reusing the hero emoji.
- Ecosystem logos under the authoring strip, npm lit, the rest dim.
- Rose selection colour on the page.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
- Every band heading now uses one clamp scale. The page had four.
- The publish band gets the same vertical rhythm as its neighbours.
- Community was dropped from the site, so the featured band points at
  /collections and its copy stops naming a directory we no longer sell.
- The hero dot underline was invisible at 35%. It reads now.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
A copy review found claims the page could not support:

- Trending is measured from posts. It never counts installs.
- "Stars are the only count we show" was false beside three counters.
- "every agent" overclaimed under a row naming seven agents.
- "stack" and "hot" are banned for collection and trending.
- Two bands repeated each other word for word.
- The sign-in wall behind "Watch your starred repos" was invisible.
- /collections 301s to /community, so three links took a redirect.
- Official publishers has its own page. The label now goes there.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
- The page had two left edges. Hero, doors and every band now share
  max-w-6xl, so nothing steps right when you scroll past the doors.
- Each band drew its edge twice, a solid border plus the dotted rule.
  The dotted rule is the divider now, and the last band drops it.
- Doors get symmetric 2.5rem padding, half the band rhythm.
- Watermarks anchor to the text column and fade at the far end, so the
  crisp end is the one beside the heading.
- The rail scrollbar and half-faded top row are gone. Vue scoped styles
  outrank a two-class selector, so the override takes three.
- Dimmed chips read as disabled UI. Tiles recede on their border now.
- Two h3 steps under the heading scale, not four.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
/api/clusters sorts tracks by how many skills each one holds, which is
supply. The grid now takes a demand order instead.

Measured 2026-09-04 from production: the 50 named skills trending over a
week, the 50 over a month and the top 50 repos by stars. 48% of trending
skills have no classifier category, so all 80 named skills were read and
placed by hand. Scored with a 1/log2(rank) decay, weighted week 1.0,
month 0.8, all-time 0.6.

Design leads at 16.5%, then agent workflows and anti-slop writing. SEO
scored zero on every board and drops off the twelve-tile grid.

Two gaps the order cannot fix: diagrams and codebase maps took 10.8% and
the top skill of the week with no track to hold them, and research came
back at 6.5% after being retired on 2026-08-13.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
The 2026-09-04 measurement changed the taxonomy, not only its order.

`diagrams` is new. Diagrams and codebase maps took 10.8% of weighted
trending demand and the top skill of the week, tt-a1i/archify, while
their skills scattered across three tracks that promise something else.
Nine pins in production, no classifier category to stand on yet.

`research` returns 22 days after it was culled for having no demand. It
now carries 6.5%, led by browser-use at rank 6 for the week. It takes
`scraping` from Agent workflows, since values may not repeat, so it has
37 classified skills under its 8 pins. Its 301 leaves nuxt.config.

Nothing retired against them, which breaks the subtraction principle on
purpose: a page answers search demand and costs a URL, a tile answers
trending demand and costs the scarcest space on the site. The grid caps
at twelve, so Performance, Everyday coding and SEO keep their pages and
lose their tiles.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
The open item from the demand measurement: /skills/diagrams ran on nine
pins with no backfill, because no classifier value described a picture.

- `diagramming` joins ABSTRACTNESS_CATEGORIES with two prompt rules: what
  counts as drawing a structure, and that a chart is not a diagram.
- scripts/backfill-diagramming-category.ts asks the same model the same
  question about a keyword-swept candidate set, instead of bumping the
  prompt version and putting 23k rows through the cron at 20 an hour.
  Dry run by default. It writes abstractness_category and nothing else,
  so a later cron pass can overrule it.
- The 8b model calls plotting skills diagramming often enough to matter,
  so a pure guard holds a verdict back when the text talks about charts
  and never about structure. Tested.
- Ran against production: 281 candidates, 111 now diagramming, 10 held.
  53 of them had no category at all before. The track has 52 skills
  behind its pins where it had none.

The track cards now carry the depth they are ranked on: a skill count on
every card and a rose mark on a track admitted in the last 45 days.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
The cap set three grid rows and clipped the rest with overflow. In one
column that is three tracks visible and nine gone, and the nine stayed
in the tab order, so keyboard focus moved to cards nobody could see.

The cap is a slice now. The component reads the rendered column count
and renders rows x columns cards, leaving a single column uncapped. The
server still renders every card, so a crawler sees all twelve.

Measured: 1 column 12 cards, 2 columns 6, 3 columns 9, 4 columns 12.

Also drops a claim that would rot: the diagrams curator note said the
top trending skill of the week sits there, under a curator's name.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
Two more of the defects the track band had.

- The first door pointed at #discover, but the trending band hides
  itself below three repositories, so the link did nothing whenever the
  band was absent. It now falls back to /skills/trending.
- "All publishers" pointed at /skills/official, which 301s to /skills,
  and the panel beside it already links there. The button is gone and
  the empty state points straight at /skills.

Swept the rest of the page while I was in there: no duplicate ids, no
image without alt text, no control without an accessible name, no
horizontal overflow at 390, 768 or 1440, and no focusable element
clipped out of view. Reduced motion stops the packet and the rail's
auto-scroll. The hero copy button and the search typeahead both work.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
InstallCommand renders a code element with no click behaviour, so the
tabindex on the authoring command put keyboard focus on something inert,
and a phone could only copy it by selecting text. It has a copy button
now, with the live region the hero already had, and the tabindex is gone
because that command wraps rather than scrolls.

Below 30rem the feed heading and its link shared one row, which wrapped
"Recently updated" onto two lines beside a button. They stack now.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
A section-by-section audit of the bands the earlier passes had not
reached. The theme repeats: copy that outran the data.

- recent-publishes joined skills and repos with LEFT JOIN, so an
  activity row whose skill was deleted survived, and a fallback invented
  a repo name for it. The feed could link to a page that does not exist.
  It is an inner join now, like recent-updates always was.
- "One digest says what changed and why" ships without the why:
  DIGEST_AI_SUMMARY_PAUSED is true, so the email carries raw commit
  messages. Both places now say the digest lists what changed.
- The lead collection card could print "Install all 0 skills" beside a
  live copy button, because the API filters members on trust and render
  status after counting them. The lead is now the first collection that
  has skills.
- The Tasted chip asserted a review no field records, three lines above
  a named curator. Removed until something records it.
- "Owner verified" is an owner allowlist, not a per-skill check. It says
  "From official owners".
- /community lists curators, not collections, so the two buttons that
  promised collections now say curators.
- A collection with seven authors read as three, and a ten-skill list
  showed six with nothing saying so. Both carry "+N more" now.
- The page description said devs were installing. Nothing measures
  installs; the feed measures posts.
- The authoring strip said skilld drafts the skill, which contradicts
  the comment four lines above it. The reader's agent drafts it.

Production smoke gains the two new track pages, and a check that every
featured collection answers 200 at its own URL. A dozen retired
collection slugs are 301s, nothing stops one being featured again, and
the homepage would link and print an install command for it.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
The hero command is the page's primary call to action and its copies
were invisible. It used raw useClipboard, which records nothing and
swallows a failure in a browser that blocks writes. Both the hero and
the authoring command go through useInstallCopy now.

That needed a third install target. The hero installs a whole
repository, and filing it as a skill would record a skill name that
does not exist, so `repo` joins the kind enum and stores the repository
in `name`. Verified end to end: a hero copy writes
home-hero / repo / obra / superpowers / install.

scripts/seed-trending-local.ts writes the social evidence the trending
band needs. Only the discovery cron writes x_posts, so a local database
has none and the band hides itself. The site's top call to action was
the one surface nobody could see while working on it. Local only,
prefixed ids, --clean removes them.

Also: an in-page jump landed a heading flush against the sticky header,
the lead collection now shows when it was last updated, and GLOSSARY
gains `track`, because one concept was called track in the UI, cluster
in the code and category in the database.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
The weekly band embeds the real email card as a picture, cropped by
overflow. Its own comment says tabbing through an inbox screenshot is a
trap, and it set aria-hidden and tabindex="-1" to prevent that. Neither
does: a negative tabindex takes the container out of the tab order and
leaves every link inside the email markup in it, and aria-hidden over
focusable children is its own violation.

Five controls were reachable and invisible on the live homepage: a skill
link, "Browse trending Skills", "Share this week's board", "Settings"
and "Unsubscribe". The frame is `inert` now, which is what the comment
was reaching for, with a test that fails on the old attribute.

Four buttons to /community carried four labels, two of them naming "the
community" as a proper noun, which GLOSSARY bans. They say curators now,
matching the page they open.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
Swept the live routes for the defects the homepage had.

- The skill page renders the file tree twice, floating and inline, and
  every folder id was built from the path alone. Six ids appeared twice
  on one page, and aria-controls resolves to the first match, so the
  second tree's buttons expanded the first tree's folders. Each tree now
  carries a useId prefix.
- "Browse categories" and two meta strings called a track a category,
  which GLOSSARY now bans in the interface. They say track.
- "Sign Up" on the trending page was the only title-case label on the
  site, for a button that starts GitHub OAuth. It says sign in with
  GitHub, which is what happens.

Checked and left alone: the trending page's email preview is an iframe,
and keyboard focus does not enter it across 120 tabs, so its inert and
tabindex pair is doing the job. /skills, /community, a track page and a
collection page are clean on traps, names, alt text, ids and overflow.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
/alt becomes /. The prototype has carried the pivot for a while and
nothing about it is alternative any more, so the file moves, the `alt-`
class prefix becomes `home-`, and the noindex comes off. `/alt` 301s to
the page it became.

`alt-page` folded into the `home-page` class the root already carried,
and the duplicate `home-hero-grid` rule merged into the original.

The previous homepage is gone with its weekly email band and its
person-authored sections. The new page answers the same two loops in
the order the pivot chose: find what devs are sharing, pick a track,
watch what changes.

Production smoke now asserts the hero's own words on `/`, because the
error branch renders an h1 too, and checks the /alt redirect.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
A visitor who clicks off the homepage should not leave the brand behind.
Three parts travel, and only three, so inner pages keep their own shape.

- Rose selection colour, everywhere. It was homepage-only.
- Band edges print in the dot grid site wide. The border keeps its place
  in the box model and gives up its colour, so nothing reflows.
- Ledger numerals are inked rose and printed in the same dot grid as the
  homepage's taste-test numbers, on track pages and the trending board.

The trending board's ranks encode a documented decision: the top three
read first, through contrast rather than type scale. That mechanism
survives as opacity, so the emphasis is unchanged and the numerals join
the brand.

Vocabulary followed the GLOSSARY entry this branch added. The curators
page called itself Community, which the glossary bans as a proper noun,
and counted creators, which it bans for a person. Two eyebrow labels on
the brand-kit page were stacked above headings, which DESIGN.md bans and
scripts/check-eyebrows.ts catches. That checker is clean now.

Claude-Session: https://claude.ai/code/session_01V83L3sonjzJF9D8aNBRhgJ
# Conflicts:
#	layers/registry/server/data/clusters.ts
#	nuxt.config.ts
The three-row fit moved from the taxonomy to the twelve-tile grid cap, so
the count grows to 14 with diagrams and research, and security stays
inside backend-data as #134 decided.

Claude-Session: https://claude.ai/code/session_017kaxoymhg5taApymPzyx1b
@harlan-github-agent harlan-github-agent Bot added harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. labels Sep 7, 2026
@harlan-github-agent

harlan-github-agent Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

🤖 MERGED

Harlan Agent Kit posted this automated review. It is not Harlan's personal review or approval. AI open source policy. Last updated: 2026-09-07 06:04 UTC.

GitHub merged this pull request. No further automated Review will run.

@harlan-github-agent harlan-github-agent Bot added harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-blocked The automated Review found a material defect in this head commit. and removed harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. labels Sep 7, 2026
The band moved from tabindex=-1 to inert so the email links leave the tab
order too, and the hero search is a new combobox that needs its own axe
pass.

Claude-Session: https://claude.ai/code/session_017kaxoymhg5taApymPzyx1b
@harlan-zw
harlan-zw merged commit 8b1af9f into main Sep 7, 2026
5 checks passed
@harlan-zw
harlan-zw deleted the feat/alt-homepage branch September 7, 2026 06:00
@harlan-github-agent harlan-github-agent Bot added harlan-agent-running An Agent holds a Task on this issue or pull request right now. and removed harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-blocked The automated Review found a material defect in this head commit. labels Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant