gabriel / musehub public
Open #104 Enhancement
filed by gabriel human · 72 days ago

Symbol anchors UX: make it consistent across the entire app

0 Anchors
Blast radius
Churn 30d
0 Proposals

Symbol anchors UX: make it consistent across the entire app

Background

The issues detail page has a polished, production-quality Symbol Anchors panel:

  • Each anchor is a clickable deep-link that navigates directly to the symbol in the blob view (ar.blob_url())
  • Addresses are parsed and structured via parse_symbol_anchor() — rendered as file_path :: symbol_name, not a raw truncated string
  • Cross-repo references are handled — owner/repo::file::symbol displays with the repo prefix
  • Each anchor carries live intelligence stats: blast radius (⬡ N), churn last 30d (↻ N), last author — sourced from anchor_intel
  • A arrow gives a clear visual affordance that the item is navigable

The mist detail page previously had a symbol anchors panel that was the opposite: raw address strings, truncated to 50 chars, all starting with the filename so they looked identical, and not clickable. It has been removed (issue #103 covers replacing it with a proper markdown TOC). But the underlying problem is architectural: there is no shared component for symbol anchors — each page that wants to show them has to reimplement the panel from scratch, and the mist/proposal pages never got the full implementation.

The correct fix is to extract the issues-page symbol anchors panel into a reusable Jinja2 macro and apply it consistently everywhere symbol anchors can appear.

Goal

  • One canonical symbol anchors panel implementation, defined once
  • Applied consistently to: mist detail, proposal detail, and any future page that surfaces symbol_anchors
  • Issues detail page continues to work exactly as today — it becomes the reference implementation
  • No page-specific hacks, no raw address strings, no truncation

Existing superior implementation (issues detail)

Source: musehub/templates/musehub/pages/issue_detail.html, around line 174

{% for anchor in symbol_anchors %}
  {%- set ar = parse_symbol_anchor(anchor) %}
  {%- set blob_href = ar.blob_url(site, owner, repo_slug) %}
  {%- set ai = anchor_intel.get(anchor, {}) %}
  <a href="{{ blob_href }}" class="isd-anchor{% if ar.is_cross_repo %} isd-anchor--cross-repo{% endif %}">
    <span class="isd-anchor-addr">
      {%- if ar.is_cross_repo %}
        <span class="isd-anchor-repo">{{ ar.owner }}/{{ ar.repo }}</span>
        <span class="isd-anchor-sep">::</span>
      {%- endif %}
      <span class="isd-anchor-file">{{ ar.file_path }}</span>
      {%- if ar.symbol_name %}
        <span class="isd-anchor-sep">::</span>
        <span class="isd-anchor-sym">{{ ar.symbol_name }}</span>
      {%- endif %}
      {%- if ar.ref %}<span class="isd-anchor-ref">@{{ ar.ref }}</span>{%- endif %}
    </span>
    <span class="isd-anchor-stats">
      {%- if blast is not none %}<span class="isd-anchor-stat" title="Blast radius">⬡ {{ blast }}</span>{%- endif %}
      {%- if churn is not none %}<span class="isd-anchor-stat" title="Churn last 30d">↻ {{ churn }}</span>{%- endif %}
      {%- if ai.last_author %}<span class="isd-anchor-author">{{ ai.last_author }}</span>{%- endif %}
    </span>
    <span class="isd-anchor-arrow">↗</span>
  </a>
{% endfor %}

This is the design spec. The macro must produce identical output.

Design

Shared macro

Extract the above into a reusable macro in a new partial:

musehub/templates/musehub/partials/symbol_anchors_panel.html
{% macro symbol_anchors_panel(symbol_anchors, anchor_intel, owner, repo_slug) %}
  {% if symbol_anchors and symbol_anchors | length > 0 %}
    <div class="ms-sidebar-card">
      <div class="ms-sidebar-card-title">
        {{ icon("radio", 13) }}
        Symbol Anchors
        <span class="ms-sidebar-count">{{ symbol_anchors | length | fmtnum }}</span>
      </div>
      <div class="ms-anchor-list">
        {% for anchor in symbol_anchors %}
          {%- set ar = parse_symbol_anchor(anchor) %}
          {%- set site = site_base_url() %}
          {%- set blob_href = ar.blob_url(site, owner, repo_slug) %}
          {%- set ai = anchor_intel.get(anchor, {}) if anchor_intel else {} %}
          <a href="{{ blob_href }}" class="ms-anchor{% if ar.is_cross_repo %} ms-anchor--cross-repo{% endif %}">
            <span class="ms-anchor-addr">
              {%- if ar.is_cross_repo %}
                <span class="ms-anchor-repo">{{ ar.owner }}/{{ ar.repo }}</span>
                <span class="ms-anchor-sep">::</span>
              {%- endif %}
              <span class="ms-anchor-file">{{ ar.file_path }}</span>
              {%- if ar.symbol_name %}
                <span class="ms-anchor-sep">::</span>
                <span class="ms-anchor-sym">{{ ar.symbol_name }}</span>
              {%- endif %}
              {%- if ar.ref %}
                <span class="ms-anchor-ref">@{{ ar.ref }}</span>
              {%- endif %}
            </span>
            {%- set blast = ai.blast if ai.blast is defined and ai.blast is not none else none %}
            {%- set churn = ai.churn_30d if ai.churn_30d is defined and ai.churn_30d is not none else none %}
            <span class="ms-anchor-stats">
              {%- if blast is not none %}<span class="ms-anchor-stat" title="Blast radius">⬡ {{ blast | fmtnum }}</span>{%- endif %}
              {%- if churn is not none %}<span class="ms-anchor-stat" title="Churn last 30d">↻ {{ churn | fmtnum }}</span>{%- endif %}
              {%- if ai.last_author is defined and ai.last_author %}<span class="ms-anchor-author">{{ ai.last_author }}</span>{%- endif %}
            </span>
            <span class="ms-anchor-arrow">↗</span>
          </a>
        {% endfor %}
      </div>
    </div>
  {% endif %}
{% endmacro %}

Route changes

Each route that renders a page with symbol anchors must supply anchor_intel — the dict of {anchor_address: {blast, churn_30d, last_author}} already computed for the issues page. Where that data is not yet fetched, pass an empty dict ({}) as a safe default — the panel will render without stats but still be fully clickable.

Pages and routes to update:

Page Route file Status
Issue detail ui_issues.py (or equivalent) ✅ already correct — reference impl
Mist detail mists.py ❌ panel removed; needs macro + anchor_intel from route
Proposal detail proposal route ❌ unknown — audit needed

CSS

The existing isd-anchor* classes on issues are page-scoped. The macro uses ms-anchor* (the global namespace). Either:

a) Alias isd-anchorms-anchor in CSS (one rename, both pages consistent), or b) Duplicate the styles under ms-anchor* and remove isd-anchor* later

Option (a) is cleaner — do it in one pass.

Phases

Phase 1 — Extract macro and update mist detail

  • SA_01 Create musehub/templates/musehub/partials/symbol_anchors_panel.html with the shared macro
  • SA_02 Update muse_detail.html to import and call the macro; pass symbol_anchors and anchor_intel (empty dict default) from the route
  • SA_03 Update mists.py route to compute and pass anchor_intel for mist symbol anchors
  • SA_04 CSS: ensure ms-anchor* styles are defined (alias or copy from isd-anchor*)
  • SA_05 Smoke test: open a mist with symbol_anchors on staging — confirm panel renders, links resolve to correct blob+symbol URL

Phase 2 — Proposal detail

  • SA_06 Audit proposal detail template for existing symbol anchors rendering
  • SA_07 Apply macro to proposal detail; update route to pass anchor_intel
  • SA_08 Smoke test: open a proposal with linked symbols on staging

Phase 3 — Issues page CSS cleanup

  • SA_09 Migrate isd-anchor*ms-anchor* in issue_detail.html and its CSS
  • SA_10 Verify issue detail page is visually identical after the rename

Acceptance criteria

  • A mist with symbol_anchors shows the same panel design as the issues page: clickable links, file :: symbol structure, blast/churn stats where available
  • A mist with no symbol_anchors (or an empty list) shows no panel — no empty card
  • A proposal with symbol_anchors shows the same panel
  • There is exactly one place in the template codebase where the symbol anchors panel is defined

Out of scope

  • Adding symbol anchor extraction to mists that don't already have them (separate feature)
  • The markdown heading TOC for mists — that is issue #103
  • Blob view symbol navigation — that is issue #100 (#::Name syntax)
Activity
gabriel opened this issue 72 days ago
No activity yet. Use the CLI to comment.