Arrow glyphs on rows, jump links, sort headers, and the category toggle, plus the search icon, were exposed to assistive tech, adding noise to every row and link name.
Co-Authored-By: Claude <noreply@anthropic.com>
The hint's column capped at 28rem, so "Press / to search. Tags open their category. Click a row for details." wrapped to two lines beside the heading.
Co-Authored-By: Claude <noreply@anthropic.com>
Sections faded and slid in as they scrolled into view, so screenshots and quick scrolls caught the sponsor band and search box half-drawn.
Co-Authored-By: Claude <noreply@anthropic.com>
The homepage printed each description inside its expanded row as .expand-desc, styled apart from the .desc-row that category pages use, so every spacing change to the table had to be tuned twice. The homepage now reveals the same .desc-row while a row is open, so its descriptions take the category pages' 15.2px size, and the .expand-desc markup and styles are gone.
Co-Authored-By: Claude <noreply@anthropic.com>
Copy told visitors to tap a tag to filter and mentioned a filter although tags are plain links and no filter exists; casing and terminology were inconsistent.
Co-Authored-By: Claude <noreply@anthropic.com>
"Indie Dev from $99 a month" put "from" on one tier although every tier discounts longer terms, the duration line listed terms the tier cards don't price, and the independence note left out the site placement.
Co-Authored-By: Claude <noreply@anthropic.com>
On phones the hero stats ran together as one sentence, breaking wherever space ran out and leaving a "/" dangling at a line end.
Co-Authored-By: Claude <noreply@anthropic.com>
At phone width the two primary buttons labelled with the full address wrapped over three lines, and the address in the Get started sentence broke at its hyphen.
Co-Authored-By: Claude <noreply@anthropic.com>
On narrow phone heroes the sheen, a diagonal light stripe sliding across for 54s after load, rendered as a near-vertical band sweeping sideways that read as a loading or progress bar.
Co-Authored-By: Claude <noreply@anthropic.com>
The sponsor copy bolded two phrases in the README call to action, repeated tier prices already in the tier headings, repeated one phrase across three Content tiers, and had a double "or" in the Duration line.
Co-Authored-By: Claude <noreply@anthropic.com>
SPONSORSHIP.md tells sponsors their links carry UTM tags, but the /sponsorship/ page, which mirrors it, left that out.
Co-Authored-By: Claude <noreply@anthropic.com>
The longer billing lines added with the annual prices broke across two or three lines beside the price and split amounts from their units.
Co-Authored-By: Claude <noreply@anthropic.com>
On phones the hero's 74-link category grid was 1,236px tall and pushed the project table down to 2,583px, over three screens.
Co-Authored-By: Claude <noreply@anthropic.com>
Below 960px the table hid the PyPI Downloads column but kept the default downloads sort, so the visible Stars column looked unsorted and the active sort header was not shown or tappable.
Co-Authored-By: Claude <noreply@anthropic.com>
The sponsor band rendered only on the homepage, so visitors who entered on a category page and never opened the homepage saw no sponsors. Category, subcategory, group, and Stdlib pages now show the same band between the hero and the table.
Co-Authored-By: Claude <noreply@anthropic.com>
The sponsor band markup lived inline in index.html, so another page could show it only by copying the markup. The rendered homepage is byte-identical.
Co-Authored-By: Claude <noreply@anthropic.com>
Featured cost $200/month, about twice Indie Dev's $99, yet offered the same text-only README entry and website link, leaving little visible reason to pick it over Indie Dev. Featured now includes a small logo next to its README entry and a logo on the website, and the Get Started content list asks Featured sponsors for a logo.
Co-Authored-By: Claude <noreply@anthropic.com>
The README sponsor CTA linked to SPONSORSHIP.md, so sponsor-page visits from the README never reached GA4.
Co-Authored-By: Claude <noreply@anthropic.com>
Mailto clicks on the sponsorship page leave no page view, so the sponsor funnel ended at the sponsorship page and never showed who went on to email. Each click now sends a sponsor_contact event with a short tier value (headline, featured, indie, or general) rather than the link URL, since GA4 caps event parameter values at 100 characters and the Indie Dev mailto href is 101.
Co-Authored-By: Claude <noreply@anthropic.com>
Picks in a "How to choose" list were plain text, so a reader had to hunt for the tool's row in the table.
Co-Authored-By: Claude <noreply@anthropic.com>
Redirect stubs for renamed category URLs carried a robots noindex meta, so Search Console filed them under "Excluded by 'noindex' tag" instead of treating the instant meta refresh as a permanent redirect, and the old URLs' ranking was not passed to the new pages.
Co-Authored-By: Claude <noreply@anthropic.com>
Group pages showed only tables, so visitors arriving from broad searches like "python ml library" got none of the which-one-to-pick guidance that each section's intro already gives.
Co-Authored-By: Claude <noreply@anthropic.com>
Homepage description rows carried aria-hidden="true" on top of hidden, unlike category pages (fixed in 528f9ed); the hidden attribute alone already keeps them from assistive tech while hidden.
Co-Authored-By: Claude <noreply@anthropic.com>
On section and subcategory pages, every row repeated its use case and current page as tags (and as the label under the name on phones), right under a heading that already said the same thing. They are now hidden in editorial order and shown again when a column sort flattens the groups.
Co-Authored-By: Claude <noreply@anthropic.com>
Clicking a category or group tag filtered the homepage table in place while rewriting the address bar to the category URL, so one URL rendered two different pages and readers never reached category pages with their intros and guides; category pages also showed a redundant self-referential filter bar.
Co-Authored-By: Claude <noreply@anthropic.com>
Category pages sorted rows by downloads, which buried editorial leads (the Django ORM showed as row 7 of 7, tkinter as 14 of 14) against CONTRIBUTING.md's "position is the marker"; the full intro in the hero pushed the table 2.5 screens down on a phone; and every page repeated the same "Search every project in one place" H2.
Co-Authored-By: Claude <noreply@anthropic.com>
Every entry description row carried aria-hidden="true", hiding descriptions that sighted readers can see from screen readers.
Co-Authored-By: Claude <noreply@anthropic.com>
Category pages carried no text of their own beyond the README one-line description, and most meta descriptions fell back to a generic "Explore N curated Python projects" line, which correlated with weak search rankings for category queries. This adds optional per-category intro markdown files rendered under the H1, with the first paragraph used as the meta description and links opening in a new tab, starting with the ORM category.
Co-Authored-By: Claude <noreply@anthropic.com>
Renamed or dissolved category slugs (e.g. /categories/web-servers/rpc/, /categories/code-analysis/code-linters/) returned 404, Search Console lists 7 of them, and each audit re-home was dropping the old URL's ranking.
Co-Authored-By: Claude <noreply@anthropic.com>
azure-sdk-for-python and google-cloud-python were rendering "Not on
PyPI", which is misleading. Both do ship on PyPI, just as many
per-service packages (azure-identity, azure-storage-blob,
google-cloud-storage, etc.) rather than under the repo name.
pypi_name_overrides.json already recorded that distinction in its
reason field; those two entries now carry an optional "badge" value
that build.py reads into the PyPI Downloads column. The other sixteen
no-count entries (cpython, renpy, agent skill repos, etc.) keep
"Not on PyPI" since that remains accurate for them.
Co-Authored-By: Claude <noreply@anthropic.com>
django.db.models, geodjango, httpx.URL and uv audit were rendering
"Not on PyPI" alongside eighteen genuinely standalone projects that
simply are not packaged on PyPI, conflating two different reasons for
a missing download count.
These four entries now carry a "(part of X)" description prefix in
README.md, mirroring the existing "(Python standard library)"
convention. build.py reads that prefix into a bundled flag that both
templates render as a "Bundled" badge.
The prefix approach was chosen over a separate data file so README.md
stays the single source of content truth, and over inferring from the
entry name because geodjango is neither dotted nor spaced and would
have been missed. Redundant tail wording was trimmed from the
httpx.URL, geodjango and uv audit descriptions now that the prefix
names the parent.
The new entry format is documented in CONTRIBUTING.md and the
vocabulary in CONTEXT.md.
Co-Authored-By: Claude <noreply@anthropic.com>
Standard-library entries rendered "Not on PyPI" in the PyPI Downloads
column, which read like missing data rather than a deliberate category
— the build already forces downloads to None for them so they never
pick up a same-named PyPI backport. They now render a "Stdlib" badge
instead, while genuine non-PyPI entries keep "Not on PyPI".
The filter tag is renamed to match, so the source-type value, the row
filter tag, and the synthetic category heading all read Stdlib now.
The literal "Built-in" strings scattered through build.py are routed
through the existing BUILTIN_FILTER constant so the label lives in one
place. The category page slug stays "built-in" so the public URL
/categories/built-in/ does not break.
Co-Authored-By: Claude <noreply@anthropic.com>
Entries now end with "(PyPI downloads/month: N, GitHub stars: M)" where
known, replacing the stars-only note, since download counts are the
list's stated primary evidence signal. annotate_entries_with_stars is
renamed to annotate_entries_with_stats and looks downloads up by the
first link's display name, skipping category-index bullets (which link
into the site itself) and Built-in entries (which would otherwise hit
same-named PyPI backports like logging or asyncio).
The intro now mirrors the README subtitle verbatim with the
project/category totals on their own line below, and the "opinionated
catalog" wording is gone since the shortlist ADR is literally titled
"shortlist, not a catalog".
Co-Authored-By: Claude <noreply@anthropic.com>
Replaces the em dash in the PyPI Downloads column with a source-badge
pill labeled "Not on PyPI", reusing the existing badge style used by
the stars column for visual consistency. Sorting is unaffected since
non-numeric cells already parse as missing.
Co-Authored-By: Claude <noreply@anthropic.com>
Downloads is now the default sort, so it sits directly after the
project name in both the index and category table templates. The
source-type badge stays in the stars cell.
Co-Authored-By: Claude <noreply@anthropic.com>
Sourced from website/data/pypi_downloads.tsv the same way
github_stars.json feeds the stars column. The new sortable column
sits between GitHub Stars and Last Commit on the homepage and
category pages, formatted with thousands separators like stars, with
an em dash when no PyPI data exists. Rows are matched by normalized
README display name; Built-in entries never show counts since
same-named PyPI packages are stdlib backports (e.g. the asyncio
package).
Below 960px the column hides and the count moves into the expand
row, mirroring the existing Last Commit treatment. main.js gains the
downloads sort branch and URL param.
The deploy workflow fetches the TSV via the new
make fetch_pypi_downloads target with a daily actions/cache
fallback, mirroring the stars fetch, but non-fatal: the column
degrades to dashes when the fetch fails, unlike stars which the
build requires.
Co-Authored-By: Claude <noreply@anthropic.com>