Releases List: Version History Mission Control
Releases List: Version History Mission Control
What the Releases List Actually Is
The releases list is not a changelog feed. It is the version history mission control for a codebase — a compressed, signal-dense timeline that answers questions no existing VCS tool can:
- Which release introduced the most breaking API changes?
- At what point did this project cross the 1.0 stability threshold?
- What fraction of v2.3.0 was written by AI agents vs humans?
- Which release resolved the most open issues?
- Did the MINOR bump in v2.4.0 involve any hotspot files?
- What's the release cadence — are we shipping faster or slower?
GitHub's releases list answers none of these. It shows a tag, a title, a date, and a download count. Muse has the data to answer all of them; the list page just hasn't surfaced it yet.
The Current State (What We Have)
The current release_list_page in
musehub/api/routes/musehub/ui_releases.py::release_list_page:
- Renders a hero card for the latest stable release
- Passes
other_releasesas a flat cursor-paginated list - Each row shows: tag, channel pill, author, relative date, commit short-SHA,
commit count from
changelog_json, GPG badge, agent badge - Client-side filter tabs (All/Stable/Pre-release/Draft) in
release-list.ts::applyFilters - Client-side search over
data-title
The fragment release_rows.html renders each row with:
.rl-row-tagwith.rl-tag--{status}class.rl-row-bodywith title, channel pill, meta row, body preview
What's missing from every row:
- Inferred bump level (
sem_ver_bumpfrom changelog_json — already parsed by_parse_changelogblast radius 94) - API surface delta counts (
api_added / api_removed / api_modifiedfromsemantic_report_json— already parsed by_parse_semantic_report) - Breaking change indicator + symbol count
- Agent/human ratio bar
- Hottest file badge (top
FileHotspotaddress) - Resolved issue + proposal count
- Download total across all assets
- Release cadence signal (days since previous)
What's missing from the list page:
- Version evolution timeline (bump history across all releases as a sparkline)
- Channel distribution summary bar
- Cadence chart (rolling average days per release)
- Cumulative API surface growth curve
- Filters by bump level, agent ratio, breaking, has-assets
- Sort modes: bump-rank, api-delta, agent-ratio, download-count, issue-count
Mission Control Layout — Full Annotated ASCII
╔══════════════════════════════════════════════════════════════════════════╗
║ RELEASES · gabriel/musehub ║
║ ────────────────────────────────────────────────────────────────────── ║
║ VERSION EVOLUTION TIMELINE (14 releases) ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ v0.1 v0.2 v0.3 v1.0 v1.1 v1.2 v2.0 v2.1 v2.2 v2.3 v2.4 ║
║ │ ○ ○ ○ ● ○ ○ 💥 ○ 💥 ○ ✨ ║
║ │ none ptch ptch MINR MINR PTCH MAJR MINR MAJR MINR MINR ║
║ │ └─ pre-1.0 zone ──────────┘ └── stable zone ────────────────┘ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
╠══════════════════════════════════════════════════════════════════════════╣
║ REPO HEALTH SUMMARY CARDS ║
║ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ║
║ │ 14 total │ │ 9 stable │ │ 3 breaking │ │ avg 8d/rel │ ║
║ │ releases │ │ 2 pre-rel │ │ releases │ │ cadence │ ║
║ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ ║
╠══════════════════════════════════════════════════════════════════════════╣
║ API SURFACE GROWTH ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ +3 +0 +1 +5 +2 +1 ─8 +3 ─5 +8 +12 ║
║ │ ▂▂ ── ▂ ▄▄ ▂▂ ▂ ██ ▂▂ ██ ▄▄ ████ (per release) ║
║ └──────────────────────────────────────────────────────────────────┘ ║
╠══════════════════════════════════════════════════════════════════════════╣
║ ★ LATEST STABLE HERO ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ v2.4.0 [stable ✓] 2026-04-14 by gabriel │ ║
║ │ "API surface stabilisation + agent provenance enrichment" │ ║
║ │ ✨ MINOR · +12 added −2 removed ~4 modified │ ║
║ │ Agent ████████ 62% · claude-sonnet-4-6 · 47 commits │ ║
║ │ [↓ 1.2k] [2 assets] [12 issues resolved] [View →] │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
╠══════════════════════════════════════════════════════════════════════════╣
║ FILTERS & SORT ║
║ [All ▾] [channel ▾] [bump ▾] [sort: newest ▾] [⊕ New Release] ║
║ ─────────────────────────────── search releases… ─────────────────── ║
╠══════════════════════════════════════════════════════════════════════════╣
║ RELEASE ROWS ║
║ ║
║ v2.3.1 [patch] [stable] 2026-04-02 ║
║ fix: wire push object pack regression ║
║ 🔧 PATCH · ~1 modified · 3 commits · Agent 0% · ↓ 840 ║
║ ────────────────────────────────────────────────────────────────────── ║
║ v2.3.0 [minor] [stable] 2026-03-28 ║
║ feat: MCP tool dispatcher + HTMX fragments ║
║ ✨ MINOR · +8 added −1 removed · 28 commits · Agent 55% ↓ 2.1k║
║ ────────────────────────────────────────────────────────────────────── ║
║ v2.2.0 [MAJOR] [stable] 2026-03-10 💥 BREAKING ║
║ breaking: MusehubIssue schema rewrite ║
║ 💥 MAJOR · −7 removed ~12 modified · 61 commits · Agent 70% ║
║ ⚠ musehub/db/musehub_models.py::MusehubIssue (+3 more symbols) ║
║ ────────────────────────────────────────────────────────────────────── ║
║ v2.1.0 [minor] [stable] 2026-02-22 ║
║ ✨ MINOR · +3 added · 19 commits · Agent 48% · ↓ 950 ║
║ ────────────────────────────────────────────────────────────────────── ║
║ v2.0.0-beta.1 [beta] [pre-release] 2026-02-10 ║
║ ○ PRE-RELEASE · draft channel · 12 commits ║
╚══════════════════════════════════════════════════════════════════════════╝
Compact Row Anatomy
┌─────────────────────────────────────────────────────────────────────────┐
│ [v2.4.0] [stable] [✨ MINOR] 2026-04-14 by gabriel 8abc1234 │
│ API surface stabilisation + agent provenance enrichment │
│ +12 ─2 ~4 │ Agent ████████ 62% │ 47 commits │ ↓ 1.2k │ 12 ◉ │ [→] │
└─────────────────────────────────────────────────────────────────────────┘
│ │ │ │ │ │ │
tag channel bump meta agent dl issues
pill pill badge row bar count resolved
Breaking release row — extra callout line:
┌─────────────────────────────────────────────────────────────────────────┐
│ [v2.2.0] [stable] [💥 MAJOR] 2026-03-10 by gabriel ── BREAKING │
│ breaking: MusehubIssue schema rewrite │
│ −7 ~12 │ Agent ██████████ 70% │ 61 commits │ ↓ 5.8k │ 7 ◉ │ [→] │
│ ⚠ symbols: musehub/db/musehub_models.py::MusehubIssue (+3 more) │
└─────────────────────────────────────────────────────────────────────────┘
Pre-1.0 zone callout:
┌─────────────────────────────────────────────────────────────────────────┐
│ [v0.3.0] [stable] [✨ pre-1.0] 2025-11-01 by gabriel │
│ pre-1.0: MAJOR bump adjusted → MINOR per semver spec │
│ +2 ─0 ~1 │ Agent 30% │ 8 commits │ ↓ 44 │
└─────────────────────────────────────────────────────────────────────────┘
Version Evolution Timeline Panel
┌──────────────────────────────────────────────────────────────────────┐
│ VERSION EVOLUTION (newest right → oldest left) │
│ │
│ tag: v0.1 v0.2 v0.3 v1.0 v1.1 v1.2 v2.0 v2.1 v2.2 v2.3 v2.4 │
│ bump: ○ ▲ ▲ ✨ ✨ 🔧 💥 ✨ 💥 ✨ ✨ │
│ none ptch ptch minr minr ptch MAJR minr MAJR minr minr │
│ API Δ: +0 +1 +2 +5 +2 +1 ─12 +3 ─7 +8 +12 │
│ days: — 12 7 3 18 9 21 14 10 8 16 │
│ │
│ Zone: ←── pre-1.0 (0.x semver rules) ──→ ←── stable ───────── │
└──────────────────────────────────────────────────────────────────────┘
API Surface Growth Sparkline
┌──────────────────────────────────────────────────────────────────────┐
│ CUMULATIVE PUBLIC API SYMBOLS OVER TIME │
│ │
│ 350 ┤ ● 354 │
│ 340 ┤ ●─────── │
│ 310 ┤ ●──────── │
│ 290 ┤ ●─────── │
│ 295 ┤ ●────────── │
│ 294 ┤ ●──── │
│ 302 ┤ ●─────── │
│ 280 ┤── │
│ └─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──── │
│ v1.0 v1.1 v1.2 v2.0 v2.1 v2.2 v2.3 v2.3.1 v2.4 │
└──────────────────────────────────────────────────────────────────────┘
Cadence Clock
┌─────────────────────────────────────────┐
│ RELEASE CADENCE │
│ │
│ Last 5 releases avg: 12.4 days │
│ All-time avg: 9.8 days │
│ │
│ █ █ █ █ █ █ █ ▄ ▂ ▂ ▃ │
│ ▲ ▲ │
│ v2.0 (21d) recent slowdown? │
└─────────────────────────────────────────┘
New TypedDict: ReleaseListPageContext
class ReleaseListPageContext(TypedDict):
"""Full server-side context for the releases list Mission Control page.
All fields are pre-resolved; templates never issue DB calls.
Fields
------
owner : str
Repository owner handle.
repo_slug : str
Repository slug.
repo_id : str
Internal repo UUID.
base_url : str
Canonical URL prefix for this repo (e.g. /gabriel/musehub).
current_page : str
Always "releases" — drives active nav link.
latest_release : ReleaseListEntry | None
Enriched hero entry; None when no releases exist.
releases : list[ReleaseListEntry]
Cursor-paginated rows (excludes hero); each row is fully enriched.
total : int
Total release count including all channels and drafts.
stable_count : int
Releases with channel == 'stable' and not is_draft.
prerelease_count : int
Releases with channel != 'stable'.
draft_count : int
Releases with is_draft == True.
breaking_count : int
Releases with at least one breaking change across all versions.
next_cursor : str | None
Tag of last row on this page; None on last page.
version_timeline : list[VersionTimelinePoint]
One entry per release (newest-first), used to render the
evolution timeline panel.
avg_cadence_days : float | None
Mean days between releases across all releases; None when < 2 exist.
recent_cadence_days : float | None
Mean days between the last 5 releases; None when < 2 of last 5 exist.
api_growth_series : list[int]
Cumulative public symbol counts at each release (oldest-first).
"""
owner: str
repo_slug: str
repo_id: str
base_url: str
current_page: str
latest_release: "ReleaseListEntry | None"
releases: "list[ReleaseListEntry]"
total: int
stable_count: int
prerelease_count: int
draft_count: int
breaking_count: int
next_cursor: "str | None"
version_timeline: "list[VersionTimelinePoint]"
avg_cadence_days: "float | None"
recent_cadence_days: "float | None"
api_growth_series: list[int]
New TypedDict: VersionTimelinePoint
class VersionTimelinePoint(TypedDict):
"""One point on the version evolution timeline.
Each entry represents one release and captures the structural
signals that explain *why* this version number was chosen.
Fields
------
tag : str
Semver tag string (e.g. "v2.4.0").
semver_str : str
Numeric portion without leading v (e.g. "2.4.0").
channel : ReleaseChannel
bump : SemVerBump
Highest bump inferred from changelog_json — "none" | "patch" |
"minor" | "major".
pre_1_0_adjusted : bool
True when the 0.x semver rules downgraded the effective bump.
is_breaking : bool
api_net_delta : int
api_added_count - api_removed_count for this release.
days_since_prev : int | None
Calendar days since the previous release's created_at; None for
the first release.
created_at : str
ISO-8601 timestamp.
"""
tag: str
semver_str: str
channel: "ReleaseChannel"
bump: "SemVerBump"
pre_1_0_adjusted: bool
is_breaking: bool
api_net_delta: int
days_since_prev: "int | None"
created_at: str
New Pure & Async Functions
def build_version_timeline(
releases: "list[MusehubRelease]",
) -> "list[VersionTimelinePoint]":
"""Project a newest-first release list into a version timeline.
Parameters
----------
releases : list[MusehubRelease]
All releases for the repo in newest-first order (as returned by
musehub_releases.list_releases).
Returns
-------
list[VersionTimelinePoint]
Same newest-first order. days_since_prev is computed by comparing
each release's created_at to the next entry (i.e. the previous
chronological release).
Notes
-----
- Pure function; no DB access, no I/O.
- Calls infer_bump_from_changelog internally; uses _parse_changelog's
blast-radius-94 output — do not re-parse changelog_json separately.
- pre_1_0_adjusted is True when semver_major == 0 and the inferred
raw bump would have been 'major' (same rule as run_suggest).
- api_net_delta is computed from semantic_report_json if present;
falls back to 0 when the report is absent or malformed.
"""
...
def compute_cadence_stats(
timeline: "list[VersionTimelinePoint]",
) -> "tuple[float | None, float | None]":
"""Derive release cadence averages from a version timeline.
Parameters
----------
timeline : list[VersionTimelinePoint]
Newest-first version timeline (from build_version_timeline).
Returns
-------
tuple[float | None, float | None]
(avg_cadence_days, recent_cadence_days) where:
- avg_cadence_days: mean of all non-None days_since_prev values.
- recent_cadence_days: mean of days_since_prev for the 5 most
recent releases; None when fewer than 2 have a delta.
Notes
-----
- Pure function; no DB access.
- Returns (None, None) when fewer than 2 releases exist.
"""
...
def build_api_growth_series(
releases: "list[MusehubRelease]",
) -> list[int]:
"""Build cumulative public symbol counts per release (oldest-first).
Parameters
----------
releases : list[MusehubRelease]
All releases in newest-first order.
Returns
-------
list[int]
One int per release in oldest-first order, representing the
total_symbols from the SemanticReleaseReport for that release.
Missing or malformed reports produce 0 for that slot.
Notes
-----
- Pure function; all data is read from semantic_report_json blobs.
- Not cumulative in the mathematical sense — each value IS the
total_symbols snapshot for that release. Rendering as a line chart
naturally shows growth/shrinkage over time.
"""
...
async def build_release_list_page_context(
db: "AsyncSession",
owner: str,
repo_slug: str,
repo_id: str,
base_url: str,
cursor: "str | None",
limit: int,
nav_ctx: "JSONObject",
) -> "ReleaseListPageContext":
"""Assemble the full ReleaseListPageContext for the Mission Control page.
Fetches all releases once, then derives all enriched data in-memory.
Total DB queries: 4 (releases, asset_stats, issue_resolution,
proposal_resolution) run concurrently via asyncio.gather().
Parameters
----------
db : AsyncSession
owner : str
repo_slug : str
repo_id : str
base_url : str
Canonical URL prefix for this repo.
cursor : str | None
Cursor tag from the previous page; None for first page.
limit : int
Rows per page (1–100).
nav_ctx : JSONObject
Navigation sidebar context from _resolve_repo.
Returns
-------
ReleaseListPageContext
Fully-populated page context ready for template rendering.
Notes
-----
- Calls prefetch_release_list_indexes() for 3 of the 4 queries.
- Never called in a per-row loop — assembles everything at page scope.
- version_timeline and api_growth_series are built over ALL releases,
not just the current page cursor window.
"""
...
New Filter & Sort Parameters
class ReleaseListFilters(TypedDict):
"""Query parameters for the releases list page.
Extends the existing cursor/limit pair with semantic filters.
Fields
------
cursor : str | None
Tag of the last row shown on the previous page.
limit : int
Rows per page; clamped to [1, 100].
channel : str | None
Filter to 'stable', 'beta', 'alpha', 'nightly'; None = all.
bump : str | None
Filter to 'major', 'minor', 'patch', 'none'; None = all.
breaking_only : bool
When True, only rows with is_breaking == True.
has_assets : bool
When True, only rows with asset_count > 0.
sort : str
One of: 'newest' (default), 'oldest', 'bump_rank',
'api_delta', 'agent_ratio', 'download_count', 'issue_count'.
q : str | None
Free-text search over tag + title + body preview.
"""
cursor: "str | None"
limit: int
channel: "str | None"
bump: "str | None"
breaking_only: bool
has_assets: bool
sort: str
q: "str | None"
Symbol Anchors
Existing Symbols (blast radius context)
| Symbol | File | Kind | Blast Radius |
|---|---|---|---|
_parse_changelog |
musehub/services/musehub_releases.py |
function | 94 callers |
_to_release_response |
musehub/services/musehub_releases.py |
function | 86 callers |
MusehubRelease |
musehub/db/musehub_models.py |
ORM class | 72 callers |
list_releases |
musehub/services/musehub_releases.py |
async function | 24 callers |
_parse_semantic_report |
musehub/services/musehub_releases.py |
function | touches all semantic report consumers |
release_list_page |
musehub/api/routes/musehub/ui_releases.py |
async function | primary rewrite target |
initReleaseList |
src/ts/pages/release-list.ts |
function | client-side filter/search |
applyFilters |
src/ts/pages/release-list.ts |
function | client-side row hide/show |
.rl-page / .rl-hero-shell |
src/scss/_releases.scss |
CSS rules | all release list styling |
enrich_release_list_entry |
musehub/services/musehub_releases.py |
function | new (issue #6 dependency) |
infer_bump_from_changelog |
musehub/services/musehub_releases.py |
function | new (issue #6 dependency) |
prefetch_release_list_indexes |
musehub/services/musehub_releases.py |
async function | new (issue #6 dependency) |
Hotspot Context (from muse code hotspots)
Top churned symbols across musehub:
| Changes | Symbol |
|---|---|
| 18 | musehub/mcp/dispatcher.py::_call_tool |
| 14 | docs/reference/type-contracts.md |
| 13 | musehub/services/musehub_wire.py::wire_push_objects |
| 13 | musehub/api/routes/musehub/ui_issues.py::issue_detail_page |
| 8 | musehub/api/routes/musehub/ui_issues.py::issue_list_page |
The ui_issues.py entries (8 and 13 changes) indicate that the issue
detail + list rewrites in issues #4 and #5 are the current high-churn zone.
The releases list page sits at 0 churn — it's stable legacy code ready for
a full reimagination without merge conflicts.
New Symbols to Create
| Symbol | File | Kind |
|---|---|---|
ReleaseListPageContext |
musehub/api/routes/musehub/ui_releases.py |
TypedDict |
VersionTimelinePoint |
musehub/api/routes/musehub/ui_releases.py |
TypedDict |
ReleaseListFilters |
musehub/api/routes/musehub/ui_releases.py |
TypedDict |
build_version_timeline |
musehub/services/musehub_releases.py |
function |
compute_cadence_stats |
musehub/services/musehub_releases.py |
function |
build_api_growth_series |
musehub/services/musehub_releases.py |
function |
build_release_list_page_context |
musehub/services/musehub_releases.py |
async function |
release_timeline_fragment |
musehub/templates/musehub/fragments/ |
HTMX fragment |
release_cadence_fragment |
musehub/templates/musehub/fragments/ |
HTMX fragment |
_enrich_all_releases |
musehub/api/routes/musehub/ui_releases.py |
private helper |
initReleaseTimeline |
src/ts/pages/release-list.ts |
function |
initReleaseCadence |
src/ts/pages/release-list.ts |
function |
.rl-timeline-panel |
src/scss/_releases.scss |
CSS rule |
.rl-bump-badge--major etc. |
src/scss/_releases.scss |
CSS rules |
.rl-api-delta |
src/scss/_releases.scss |
CSS rule |
.rl-agent-bar |
src/scss/_releases.scss |
CSS rule |
HTMX Fragment Architecture
GET /{owner}/{repo}/releases ← full page
├── SSR: version timeline panel (inline, all releases)
├── SSR: repo health summary cards (inline)
├── SSR: API surface growth sparkline (inline)
├── SSR: hero card (latest_release)
└── hx-get: …/releases/fragment?cursor=&channel=&bump=&sort=
└── fragment: release_rows.html (cursor-paginated, enriched rows)
GET /{owner}/{repo}/releases/timeline ← lazy panel
└── fragment: release_timeline_fragment.html
GET /{owner}/{repo}/releases/cadence ← lazy panel
└── fragment: release_cadence_fragment.html
New endpoints:
GET /{owner}/{repo}/releases/timeline— full evolution timeline with bump markers, api delta sparkline, days-between-releases bar chartGET /{owner}/{repo}/releases/cadence— cadence statistics panelGET /{owner}/{repo}/releases/fragment— HTMX-swappable enriched row list (replaces existing cursor pagination; adds new filter/sort params)
8-Tier Test Plan
Tier 1: Shape
class TestReleasesListShape:
def test_release_list_page_context_has_all_required_keys(self) -> None: ...
def test_version_timeline_point_has_all_required_keys(self) -> None: ...
def test_release_list_filters_has_all_required_keys(self) -> None: ...
def test_build_version_timeline_importable(self) -> None: ...
def test_compute_cadence_stats_importable(self) -> None: ...
def test_build_api_growth_series_importable(self) -> None: ...
def test_build_release_list_page_context_importable(self) -> None: ...
def test_bump_rank_order_none_lt_patch_lt_minor_lt_major(self) -> None: ...
def test_release_channel_literals_complete(self) -> None: ...
Tier 2: Round-Trip
class TestReleasesListRoundTrip:
async def test_release_list_page_returns_200_with_releases(...) -> None: ...
async def test_release_list_page_hero_is_latest_stable(...) -> None: ...
async def test_release_rows_fragment_returns_html(...) -> None: ...
async def test_timeline_fragment_endpoint_returns_200(...) -> None: ...
async def test_cadence_fragment_returns_stats(...) -> None: ...
async def test_channel_filter_returns_only_stable(...) -> None: ...
async def test_bump_filter_returns_only_major(...) -> None: ...
async def test_breaking_only_filter(...) -> None: ...
async def test_sort_by_api_delta(...) -> None: ...
Tier 3: Edge Cases
class TestReleasesListEdgeCases:
async def test_empty_repo_renders_zero_state(...) -> None: ...
def test_build_version_timeline_single_release_has_none_days_since_prev(...) -> None: ...
def test_build_version_timeline_pre_1_0_adjusted_flag(...) -> None: ...
def test_compute_cadence_stats_none_when_fewer_than_2_releases(...) -> None: ...
def test_compute_cadence_stats_recent_uses_last_5(...) -> None: ...
def test_build_api_growth_series_zero_on_empty_report(...) -> None: ...
def test_build_api_growth_series_oldest_first_order(...) -> None: ...
def test_cursor_excludes_earlier_seen_rows(...) -> None: ...
async def test_all_drafts_excluded_from_public_list(...) -> None: ...
def test_api_net_delta_negative_for_removal_heavy_release(...) -> None: ...
Tier 4: Stress
class TestReleasesListStress:
async def test_release_list_page_100_releases_under_500ms(...) -> None: ...
def test_build_version_timeline_1000_releases_under_100ms(...) -> None: ...
def test_compute_cadence_stats_1000_entries_under_10ms(...) -> None: ...
def test_build_api_growth_series_1000_releases_under_50ms(...) -> None: ...
async def test_prefetch_indexes_100_ids_issues_exactly_3_queries(...) -> None: ...
async def test_timeline_fragment_with_200_releases_under_300ms(...) -> None: ...
Tier 5: Data Integrity
class TestReleasesListDataIntegrity:
def test_breaking_count_in_context_matches_actual_releases(...) -> None: ...
def test_version_timeline_length_equals_total_releases(...) -> None: ...
def test_api_growth_series_length_equals_release_count(...) -> None: ...
def test_days_since_prev_correct_for_known_dates(...) -> None: ...
def test_avg_cadence_is_mean_of_all_deltas(...) -> None: ...
def test_timeline_newest_first_order_preserved(...) -> None: ...
def test_stable_count_in_context_matches_filter(...) -> None: ...
def test_api_net_delta_equals_added_minus_removed(...) -> None: ...
Tier 6: Performance
class TestReleasesListPerformance:
def test_build_version_timeline_50_releases_under_5ms(...) -> None: ...
def test_compute_cadence_stats_single_call_under_1ms(...) -> None: ...
def test_build_api_growth_series_50_releases_under_5ms(...) -> None: ...
def test_enrich_release_list_entry_single_call_under_1ms(...) -> None: ...
Tier 7: Security
class TestReleasesListSecurity:
@pytest.mark.parametrize("tag", ["<script>alert(1)</script>", "v1.0.0\x00hidden", ...])
def test_tag_sanitised_in_list_row(...) -> None: ...
async def test_invalid_sort_param_defaults_to_newest(...) -> None: ...
async def test_invalid_channel_param_rejected_or_ignored(...) -> None: ...
def test_release_title_html_escaped_in_row(...) -> None: ...
def test_author_xss_not_rendered_raw(...) -> None: ...
def test_breaking_symbol_address_sanitised(...) -> None: ...
def test_agent_id_with_null_byte_sanitised(...) -> None: ...
def test_q_search_param_not_reflected_unsanitised(...) -> None: ...
Tier 8: Docstrings
class TestReleasesListDocstrings:
def test_release_list_page_context_docstring_lists_all_fields(...) -> None: ...
def test_version_timeline_point_docstring_lists_all_fields(...) -> None: ...
def test_release_list_filters_docstring_lists_all_fields(...) -> None: ...
def test_build_version_timeline_docstring_covers_pure_note(...) -> None: ...
def test_build_version_timeline_docstring_covers_pre_1_0(...) -> None: ...
def test_compute_cadence_stats_docstring_covers_none_return(...) -> None: ...
def test_build_api_growth_series_docstring_covers_zero_fallback(...) -> None: ...
def test_build_release_list_page_context_docstring_covers_query_count(...) -> None: ...
def test_all_new_symbols_importable(...) -> None: ...
7-Phase Implementation Plan
Phase 1: Service Layer — Timeline & Cadence
Files: musehub/services/musehub_releases.py
- Add
build_version_timeline(releases)— pure function over all releases - Add
compute_cadence_stats(timeline)— pure function, returns (avg, recent) - Add
build_api_growth_series(releases)— pure function, oldest-first int list - Add
build_release_list_page_context(db, ...)— async assembler, 4 queries viaasyncio.gather()(reusesprefetch_release_list_indexesfrom issue #6) - Add
ReleaseListFiltersTypedDict (shared by route and service) - Tests: Tier 1, Tier 3 (edge cases for pure functions), Tier 5 (data integrity for timeline order, cadence math, api_net_delta), Tier 6 (micro-timing)
Phase 2: TypedDicts & Route Refactor
Files: musehub/api/routes/musehub/ui_releases.py
- Add
ReleaseListPageContextTypedDict - Add
VersionTimelinePointTypedDict - Refactor
release_list_pageto callbuild_release_list_page_context - Add
ReleaseListFiltersparsing from query params (channel, bump, sort, breaking_only, has_assets, q) with strict whitelist validation - Tests: Tier 2 (round-trip for page + fragment), Tier 7 (security for all new query params)
Phase 3: Fragment Endpoint Enrichment
Files: musehub/api/routes/musehub/ui_releases.py
- Add
GET /{owner}/{repo}/releases/fragmentendpoint — enriched, filterable, sortable HTMX row list (replaces in-page cursor logic) - Add
GET /{owner}/{repo}/releases/timelineendpoint →release_timeline_fragment.html - Add
GET /{owner}/{repo}/releases/cadenceendpoint →release_cadence_fragment.html - Tests: Tier 2 (round-trip for all 3 new endpoints), Tier 4 (stress for 100-release timeline fragment)
Phase 4: Template — Release Rows Fragment
Files: musehub/templates/musehub/fragments/release_rows.html
- Add bump badge
[💥 MAJOR]/[✨ MINOR]/[🔧 PATCH]to each row - Add API delta chip
+12 −2 ~4 - Add agent ratio micro-bar
Agent ████████ 62% - Add download count chip
↓ 1.2k - Add resolved issue count chip
12 ◉ - Add breaking change callout line with first symbol address
- Add pre-1.0 callout when
pre_1_0_adjusted - Tests: Tier 2 (round-trip for enriched fragment rendering), Tier 7 (security for all new rendered fields)
Phase 5: Template — Main Page Panels
Files:
musehub/templates/musehub/pages/releases.htmlmusehub/templates/musehub/fragments/release_timeline_fragment.html(new)musehub/templates/musehub/fragments/release_cadence_fragment.html(new)Add version evolution timeline panel (hx-get lazy loaded)
Add repo health summary cards (total, stable, breaking, avg cadence)
Add API surface growth sparkline (inline or hx-get lazy)
Add enhanced filter toolbar: channel, bump, sort, breaking_only, has_assets
Tests: Tier 2 (round-trip for timeline and cadence fragments)
Phase 6: TypeScript + SCSS
Files:
src/ts/pages/release-list.tssrc/scss/_releases.scssTypeScript:
- Add
initReleaseTimeline()— HTMX swap for lazy timeline panel - Add
initReleaseCadence()— HTMX swap for lazy cadence panel - Update
applyFilters()for server-side filters (POST-back via hx-get on filter change) vs client-side search for q=
- Add
SCSS:
.rl-bump-badge--major(red),--minor(blue),--patch(grey),--none(muted).rl-api-delta—+Ngreen,−Nred,~Namber.rl-agent-bar— gradient fill proportional to agent_ratio_pct.rl-breaking-callout— yellow/orange warning row.rl-pre-1-0-zone— muted overlay for timeline pre-1.0 section.rl-timeline-panel— full evolution timeline container.rl-cadence-panel— cadence stats container.rl-api-sparkline— growth chart row
Tests: Tier 4 (render stress), Tier 7 (security for reflected params)
Phase 7: Stress, Security & Docstring Hardening
- Tier 4 stress tests (100-release page under 500 ms, 1000-entry timeline under 100 ms, 3-query prefetch assertion)
- Tier 7 security parametrised tests for all new query params + display fields
- Tier 8 docstring assertions for all 4 new service functions + 3 new TypedDicts
- Verify
build_release_list_page_contextissues exactly 4 DB queries
What Makes This Uniquely Muse
Bump provenance on every row. Each list row shows
[✨ MINOR]or[💥 MAJOR]— not as a human-assigned label, but as the inferred result of structural AST diff analysis. No other VCS list page does this.API surface delta at a glance.
+12 −2 ~4on every row tells you whether your public contract grew, shrank, or was refactored without opening the release at all.Version evolution timeline. The complete version history rendered as a bump-annotated sparkline, with a visible boundary between the pre-1.0 (unstable) zone and the stable zone. The semver spec's 0.x rules are visually encoded in the timeline.
Release cadence as a first-class signal. Are we shipping faster or slower? Is the cadence diverging from the historical average? These are answered on the list page, not in a spreadsheet.
Agent/human ratio per release. In the agent-first era, the percentage of commits authored by AI agents is as meaningful as "how many commits" — and it varies dramatically between patch hotfixes (likely human) and feature releases (likely agent-heavy).
Breaking change callout without opening the release. The first breaking symbol address is shown inline on the row, with a
(+N more)overflow. Teams can scan for breaking releases across 50 rows in seconds.Filterable by semantic signals. Filtering by
bump=majororbreaking_only=1uses derived intelligence, not tags or labels that a human chose. The filter results are structurally guaranteed.