- Wrap category pages in a self-contained @graph (WebSite + CollectionPage)
- Set canonical @id on CollectionPage to its URL (no hash fragment)
- Expand isPartOf to typed object {"@type": "WebSite", "@id": ...}
- Extract _website_node() and ISPARTOF_WEBSITE constants to avoid repetition
- Update tests to assert @graph structure on category pages
Co-Authored-By: Claude <noreply@anthropic.com>
Drops tests that either duplicate coverage already provided by adjacent
cases (single-word slugify, trailing-slash checks) or hard-code first-
category names and specific description strings that break whenever the
README content shifts.
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a dedicated sponsorship page at /sponsorship/ built from the Jinja2
template, with hero stats, tier cards, and CSS. Updates the index.html
sponsor sidebar link to point to /sponsorship/ instead of the GitHub
SPONSORSHIP.md. Adds the URL to the sitemap and test fixtures.
Also renames .impeccable.md to DESIGN.md.
Co-Authored-By: Claude <noreply@anthropic.com>
Add search input, filter chips, no-results block, and back-to-top
button to category/group/subcategory pages. Pass filter_urls_json to
all page types so tag-chip navigation works site-wide. Fix JS so
filter-clear and no-results-clear redirect to / on non-index pages
instead of trying to filter a non-existent local table. Remove the
now-redundant .category-results CSS overrides.
Co-Authored-By: Claude <noreply@anthropic.com>
`| safe` bypasses Jinja autoescape. If a category name ever contained
"</script>", the literal substring would close the script block early,
leaking JSON content into the DOM and creating an XSS vector. Replace
"</" with "<\\/" (still valid JSON) and pass ensure_ascii=False so
non-ASCII names render readably. Also add a group_path() helper to
parallel category_path()/subcategory_path() and reuse category_urls
when seeding filter_urls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds filter_urls dict (categories, groups, subcategories) in build.py,
passes filter_urls_json to the template, and injects a JSON script block
before the results section in index.html. Covered by a new test that
verifies all three URL types are present and correctly resolved.
Co-Authored-By: Claude <noreply@anthropic.com>
Membership-only assertions wouldn't catch phantom URLs added by future
build changes. Tighten back to an exact-list assertion now that we know
the fixture's exact output, and assert lastmod count tracks loc count.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the .category-subtitle a underline style for visual cohesion in
the hero, and locks in the gating behavior with a negative assertion so
a regression that drops the page_kind guard would be caught.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Categories and groups will share the /categories/ URL namespace.
Fail the build with a clear error message if a future README change
introduces a collision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avoids a slug collision between the group "Miscellaneous" and the
category of the same name once both share the /categories/ URL
namespace introduced in the upcoming filter-URL refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add llms.txt Jinja2 template with a categories_md placeholder
- Extract categories body from README and inject it into the template
- Annotate bullet-entry lines with GitHub star counts (N GitHub stars)
for the main index.md and bare numbers for llms.txt
- Add TestAnnotateEntriesWithStars unit tests
Co-Authored-By: Claude <noreply@anthropic.com>
* update gitignore
* feat: tighten homepage metadata
* fix: trim generated HTML whitespace
* feat(website): add discovery files and markdown alternate
* feat(website): add sitemap lastmod
* feat(seo): add Content-Signal directive to robots.txt
Signals search, ai-input, and ai-train to crawlers
via the experimental Content-Signal header in robots.txt.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Replace os.path.join + manual open() with Path(__file__).resolve().parents[2]
and Path.read_text() for locating and reading README.md.
Co-Authored-By: Claude <noreply@anthropic.com>
The helper only appeared once and the logic is two lines, so the named
function added indirection without clarity. Removed the four dedicated
unit tests that covered the function directly.
Co-Authored-By: Claude <noreply@anthropic.com>
Add three tests against the real README: verify all entries have
non-empty names, valid http(s) URLs, and no broken markdown link
syntax (e.g. '[name(url)' missing the closing '](').
Co-Authored-By: Claude <noreply@anthropic.com>
Split _GITHUB_NAME_RE into separate owner and repo patterns.
Owner regex now rejects leading/trailing hyphens and dots (matching
GitHub's actual username rules). Repo regex requires alphanumeric
start but allows dots and underscores anywhere after.
New tests cover GraphQL injection attempts, invalid leading chars,
and valid hyphenated/underscore/dot combinations.
Co-Authored-By: Claude <noreply@anthropic.com>
Display the awesome-python repo's star count (formatted as '230k+') and
the last data refresh date below the hero CTA. Fetches the self-repo
star count by always including vinta/awesome-python in the stars fetch.
Also removes the footer date stamp, which is now surfaced in the hero.
Co-Authored-By: Claude <noreply@anthropic.com>
parse_readme now returns list[ParsedGroup] instead of a tuple. The
resources section (Newsletters, Podcasts), preview string, and
content_html rendering are no longer produced by the parser or consumed
by the build. Removes _render_section_html, _group_by_h2, and the
associated dead code and tests.
Co-Authored-By: Claude <noreply@anthropic.com>
group_categories only ever appended a Resources group when the
resources list was non-empty. All call sites passed an empty list,
making it a no-op indirection. Inline parsed_groups directly and
remove the dead code along with its tests.
Co-Authored-By: Claude <noreply@anthropic.com>
Several sections were removed in recent cleanup commits, so the previous
floor of 76 was no longer accurate.
Co-Authored-By: Claude <noreply@anthropic.com>
The website builder previously relied on a hardcoded SECTION_GROUPS list in
build.py to organize categories into thematic groups. This was fragile: any
rename or addition to README.md required a matching code change.
Replace this with a parser-driven approach:
- readme_parser.py now detects bold-only paragraphs (**Group Name**) as
group boundary markers and groups H2 categories beneath them into
ParsedGroup structs.
- build.py drops SECTION_GROUPS entirely; group_categories() now just
passes parsed groups through and appends the Resources group.
- sort.py is removed as it relied on the old flat section model.
- Tests updated throughout to reflect the new (groups, resources) return
shape and to cover the new grouping logic.
Co-Authored-By: Claude <noreply@anthropic.com>
Introduce CACHE_MAX_AGE_HOURS (12 h) and filter current_repos before
the fetch loop so repos that were updated recently are not re-requested.
Prints a breakdown of fetched vs cached count.
Co-Authored-By: Claude <noreply@anthropic.com>
Switch readme_parser.py from regex-based parsing to markdown-it-py for
more robust and maintainable Markdown AST traversal. Update build pipeline,
templates, styles, and JS to support the new parser output. Refresh GitHub
stars data and update tests to match new parser behavior.
Co-Authored-By: Claude <noreply@anthropic.com>
load_cache was a duplicate of logic now living in build.load_stars.
Switch the call site to the shared helper and remove the redundant
local function and its tests.
Co-Authored-By: Claude <noreply@anthropic.com>
slugify, parse_readme, count_entries, extract_preview, render_content_html,
and related helpers are moved to a dedicated readme_parser module.
build.py now imports from readme_parser rather than defining these inline.
Tests for the removed functions are dropped from test_build.py since they
now live with the module they test.
Co-Authored-By: Claude <noreply@anthropic.com>