gabriel / musehub public
Open #32
filed by gabriel human · 37 days ago

Releases List: Version History Mission Control

0 Anchors
Blast radius
Churn 30d
0 Proposals

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_releases as 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-tag with .rl-tag--{status} class
  • .rl-row-body with title, channel pill, meta row, body preview

What's missing from every row:

  • Inferred bump level (sem_ver_bump from changelog_json — already parsed by _parse_changelog blast radius 94)
  • API surface delta counts (api_added / api_removed / api_modified from semantic_report_json — already parsed by _parse_semantic_report)
  • Breaking change indicator + symbol count
  • Agent/human ratio bar
  • Hottest file badge (top FileHotspot address)
  • 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 chart
  • GET /{owner}/{repo}/releases/cadence — cadence statistics panel
  • GET /{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 via asyncio.gather() (reuses prefetch_release_list_indexes from issue #6)
  • Add ReleaseListFilters TypedDict (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 ReleaseListPageContext TypedDict
  • Add VersionTimelinePoint TypedDict
  • Refactor release_list_page to call build_release_list_page_context
  • Add ReleaseListFilters parsing 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/fragment endpoint — enriched, filterable, sortable HTMX row list (replaces in-page cursor logic)
  • Add GET /{owner}/{repo}/releases/timeline endpoint → release_timeline_fragment.html
  • Add GET /{owner}/{repo}/releases/cadence endpoint → 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.html

  • musehub/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.ts

  • src/scss/_releases.scss

  • TypeScript:

    • 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=
  • SCSS:

    • .rl-bump-badge--major (red), --minor (blue), --patch (grey), --none (muted)
    • .rl-api-delta+N green, −N red, ~N amber
    • .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_context issues exactly 4 DB queries

What Makes This Uniquely Muse

  1. 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.

  2. API surface delta at a glance. +12 −2 ~4 on every row tells you whether your public contract grew, shrank, or was refactored without opening the release at all.

  3. 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.

  4. 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.

  5. 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).

  6. 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.

  7. Filterable by semantic signals. Filtering by bump=major or breaking_only=1 uses derived intelligence, not tags or labels that a human chose. The filter results are structurally guaranteed.

Activity
gabriel opened this issue 37 days ago
No activity yet. Use the CLI to comment.