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>
Use the precomputed sub["url"] to identify which subcategories belong
to a category. Avoids parsing the "Cat > Sub" value string, which would
silently misfire if a category name ever contained " > ".
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>
Swaps --line-strong for --accent-underline on the 'Become a sponsor'
text-decoration-color so the underline matches the tan hover underline
on project-name links like thealgorithms.
Co-Authored-By: Claude <noreply@anthropic.com>
Swap the border-bottom + padding-bottom fake underline on .sponsor-become
for a native text-decoration underline with text-underline-offset so the
line hugs the text at the same distance as the hero @vinta/@JinyangWang27
links, rather than sitting a fixed 0.2rem gap away.
Co-Authored-By: Claude <noreply@anthropic.com>
Override font-size to var(--text-lg) inside .sponsor-meta so the
Sponsors heading is larger, while the shared .section-label class
remains --text-sm everywhere else.
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>
Eliminate the redundant _find_link_deep precheck by merging the two
walks over inline.children into one loop that simultaneously locates
the link and records its top-level index.
Co-Authored-By: Claude <noreply@anthropic.com>
Merge the two inline-renderer implementations into a single _render_inline(children, *, html) function that handles both output modes. The original public functions become one-line wrappers so all dispatch logic lives in one place. Also aligns html_inline handling: the html=True path now escapes the raw content instead of silently dropping it in the plain-text path.
Co-Authored-By: Claude <noreply@anthropic.com>
Collapse the if-seen/else-new branches so the category/group/subcategory
merge logic runs once per entry unconditionally, appending to empty lists
on first sight instead of duplicating the append logic in the else branch.
Annotate seen and entries as dict[str, Any] so ty can resolve the mixed
value types (str, list, None) in each entry dict.
Co-Authored-By: Claude <noreply@anthropic.com>
Use timedelta(hours=CACHE_MAX_AGE_HOURS) so the cache-age check
reads at the intended hours unit directly, removing the conversion
arithmetic.
Co-Authored-By: Claude <noreply@anthropic.com>
Use itertools.batched (stdlib since Python 3.12, targeted by this project)
instead of manual range(0, N, BATCH_SIZE) slicing. Loosen fetch_batch,
build_graphql_query, and parse_graphql_response signatures from list[str]
to Sequence[str] since batched yields tuples.
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>
The helper was only called once and the bool(inline.children) guard
was redundant: first_link being non-None already implies inline.children
is non-empty.
Co-Authored-By: Claude <noreply@anthropic.com>
The private helper duplicated _find_child with a hardcoded type filter.
Remove it and call the general helper directly at both call sites.
Co-Authored-By: Claude <noreply@anthropic.com>
Extracts a _href(link) helper that returns link.attrGet('href') narrowed
to str (falling back to '') instead of the raw str|int|float|None union.
Replaces all four attrGet('href') or '' call sites with _href(), fixing
ty errors where the widened union leaked into TypedDict url fields.
Co-Authored-By: Claude <noreply@anthropic.com>
Cache-write shape mismatches the TypedDict and callers mix .get() and
direct access, so the stricter type was providing false safety. Using
dict[str, dict] accurately reflects the actual runtime contract.
Co-Authored-By: Claude <noreply@anthropic.com>
Replace loose list[dict] annotations with concrete TypedDicts imported
from readme_parser so ty can verify call-site compatibility.
Co-Authored-By: Claude <noreply@anthropic.com>
client is the only non-first param and is always required, so the * separator
adds no clarity. Update the call site accordingly.
Co-Authored-By: Claude <noreply@anthropic.com>
Python 3.11 introduced datetime.UTC as a cleaner alias for
datetime.timezone.utc. Both build.py and fetch_github_stars.py
are updated to use the shorter form.
Co-Authored-By: Claude <noreply@anthropic.com>
State reset (current_group_name = None, current_group_cats = []) was
duplicated in both branches of the early-return guard. Move it after
the conditional so it runs exactly once regardless of path.
Co-Authored-By: Claude <noreply@anthropic.com>
Parse the # Sponsors heading in README.md into structured data and
render a dedicated sponsor band above the library index on the site.
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>
Rename _GITHUB_OWNER_RE and _GITHUB_NAME_RE to GITHUB_OWNER_RE and
GITHUB_NAME_RE to match the naming style of the other module-level
constants (GRAPHQL_URL, BATCH_SIZE).
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>
The flex properties (align-items, flex-direction, flex-wrap, justify-content)
were overriding the default layout unnecessarily on mobile.
Co-Authored-By: Claude <noreply@anthropic.com>