feat(badges): publish shields.io for-the-badge style as the canonical SVG#14
Merged
Merged
Conversation
… SVG
Decision: the scanner's /api/v1/skills/<ns>/<slug>/badge/{status,score}.svg endpoints now serve the tall, ALL-CAPS, pill-rounded for-the-badge style from shields.io directly, replacing the narrow inline two-rect flat SVG. registry.coder.com and any third-party README embedding the URL gets the same canonical look without going through shields.io themselves.
How: status_badge_svg() and score_badge_svg() call a new fetch_shields_io_svg() helper that hits https://img.shields.io/static/v1?label=...&message=...&color=...&style=for-the-badge with a 10s timeout and a coder-skill-scanner User-Agent (shields 403s the default urllib UA). On any non-200 or network failure they fall back to the existing _flat_badge_svg() inline renderer so a publish job never ships a missing badge.
The JSON endpoints (/badge/<name>.json) are unchanged - third-party consumers who want a different shields.io style can still hit img.shields.io/endpoint?url=<our-json>&style=<whatever> themselves.
Tests: 9 new pytest cases covering URL construction, User-Agent identification, HTTP-error and exception fallbacks, shields-first happy path, score-badge parameter flow. Existing fallback-path tests preserved via monkeypatch. 59/59 green, ruff clean. Real-payload smoke run produces 18 files, height-28 SVGs, MALICIOUS / 100/100 etc.
There was a problem hiding this comment.
Pull request overview
Updates the scanner’s SVG badge endpoints to publish shields.io-rendered for-the-badge SVGs as the canonical output (with an inline flat-style fallback) so the registry can embed the “official” look without relying on shields at view time.
Changes:
- Added a
fetch_shields_io_svg()helper plusDEFAULT_BADGE_STYLE, and switchedstatus_badge_svg()/score_badge_svg()to prefer shields.io SVGs with a local fallback. - Expanded pytest coverage for shields URL/header construction and fallback behavior, and adjusted existing SVG tests to exercise the fallback renderer.
- Relaxed the API wiring test to accept the
for-the-badgeuppercase rendering by comparing case-insensitively.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
scanner/badges.py |
Adds shields.io SVG fetching and makes shields-rendered for-the-badge the default SVG output with an inline fallback. |
tests/test_badges.py |
Updates existing SVG tests to force fallback, and adds new tests for shields fetch behavior and parameter flow. |
tests/test_api.py |
Updates badge SVG assertion to be case-insensitive due to for-the-badge uppercasing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Two Copilot review comments addressed. 1. scanner/badges.py module docstring rewritten. The old text claimed the SVG endpoint was a flat-style inline renderer with 'no shields.io dependency, no network hop' and that the two-rect flat layout was the contract. After this branch's switch to for-the-badge as the canonical SVG, that text is no longer true. New docstring lays out the two surfaces (JSON contract unchanged from v1; SVG is shields-fetched for-the-badge with an inline flat fallback) and explains why the fallback is intentionally a different visual style. 2. Per-run circuit breaker on fetch_shields_io_svg. Before: every badge in a publish job hit shields.io independently, so a slow or down shields.io could stall the job for (n_skills * n_badges) * timeout seconds in the worst case (60s for the current 3 skills * 2 badges with the 10s timeout). After: the first failure inside a run flips a module-level flag and every subsequent call short-circuits to None without touching the network, bounding the worst-case stall at exactly one timeout per publish. Added reset_shields_circuit_breaker() helper exposed for tests, an autouse pytest fixture in tests/test_badges.py that resets the flag between tests (otherwise fail-path tests would leak state into success-path tests), and two new pytest cases covering the breaker (short-circuits after first failure; resets cleanly). 61/61 green, ruff clean.
Drops ~55 net lines of prose from scanner/badges.py and tests/test_badges.py without changing behaviour. Module docstring goes from a structured essay to four lines that name the two surfaces. Per-line constant comments cut to one line each (or removed when the constant is self-explanatory like the 10s timeout). Circuit-breaker variable explanation goes from a paragraph to two lines. Log warning suffixes go from 'disabling shields fetch for the rest of this run' to 'disabling fetch this run'. Test fixture docstring goes from five lines to one. 61/61 green, ruff clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
/api/v1/skills/<ns>/<slug>/badge/{status,score}.svgnow serves the tall, ALL-CAPS, pill-roundedfor-the-badgestyle from shields.io directly, replacing the narrow inline two-rect flat SVG.status_badge_svg()andscore_badge_svg()call a newfetch_shields_io_svg()helper that hitshttps://img.shields.io/static/v1with our label/message/color/style and acoder-skill-scannerUser-Agent (shields 403s the defaulturllibUA). On any non-200 or network failure they fall back to the existing_flat_badge_svg()inline renderer so a publish job never ships a missing badge.The JSON endpoints (
/badge/<name>.json) are unchanged — third-party README authors who want a different style can still hitimg.shields.io/endpoint?url=<our-json>&style=<whatever>themselves.9 new pytest cases cover URL construction, the User-Agent header, HTTP-error and exception fallbacks, the shields-first happy path, and the score-badge parameter flow. The two existing SVG-content tests are mocked to the fallback so they keep exercising the inline renderer. 59/59 green, ruff clean. Real-payload smoke run writes 18 files of height-28 SVGs (
MALICIOUS/100/100etc).Why this was needed
The registry decision is to embed badges directly from the scanner rather than going through shields.io at view time — fewer runtime dependencies, one cache layer (the GitHub Pages CDN), and the "official" look encoded in the scanner output rather than the consumer URL. That means the scanner has to publish the SVG in the style we want, not just the JSON payload shields.io would render.
for-the-badgeis the chosen style — reviewed againstflat/flat-square/plasticin the registry-server mockup branch.This PR was prepared with help from Coder Agents.