[Atlas] Rich Citation Hover Popover for Wiki Pages

← All Specs

[Atlas] Rich Citation Hover Popover for Wiki Pages

Task Type: feature Layer: Atlas Priority: P88 Estimated effort: 2–3h File: api.py — wiki page renderer (around line 36340)

Goal

Replace the current thin citation tooltip (plain title attribute showing authors (year)) with a rich hover popover that shows the full citation context: title, claim being supported, key excerpt, and figure reference.

Current State

In api.py the wiki citation tooltip is built at render time:

const tip = (r.authors || '') + ' (' + (r.year || '') + ')';
// stored as data-ref-text on the <a> element

The mouseover handler (~line 36340) reads data-ref-text and shows a plain fixed <div> with just that string.

Target State

Popover HTML Structure

<div id="cite-popover" style="
  position:fixed; background:#1a1a2e; color:#e0e0e0;
  border:1px solid rgba(79,195,247,0.35); border-radius:8px;
  padding:0.75rem 1rem; font-size:0.82rem; max-width:380px;
  z-index:10000; pointer-events:none;
  box-shadow:0 6px 20px rgba(0,0,0,0.5); line-height:1.5;
  transition:opacity 0.12s ease;
">
  <div style="font-weight:600;color:#4fc3f7;margin-bottom:0.3rem">
    [N] Author et al. (Year) · Journal
  </div>
  <div style="color:#e0e0e0;font-style:italic;margin-bottom:0.4rem;font-size:0.8rem">
    Title of the paper
  </div>
  <!-- claim line — shown only if refs[key].claim exists -->
  <div style="background:rgba(129,199,132,0.1);border-left:2px solid #81c784;
    padding:0.3rem 0.5rem;margin-bottom:0.4rem;font-size:0.78rem;color:#c8e6c9">
    ↳ Claim: {claim}
  </div>
  <!-- excerpt — shown only if refs[key].excerpt exists -->
  <div style="color:#aaa;font-size:0.75rem;font-style:italic;margin-bottom:0.4rem">
    "{excerpt}" {figure_ref}
  </div>
  <!-- footer with link -->
  <div style="display:flex;justify-content:flex-end;margin-top:0.3rem">
    <a href="{pubmed_url}" target="_blank" style="color:#4fc3f7;font-size:0.75rem">
      PubMed →
    </a>
  </div>
</div>

Hover Behavior

  • Popover appears 150ms after mouseover of .ref-link (debounced — avoids flash on quick passes)
  • Disappears 300ms after mouseout (so user can move cursor onto popover itself)
  • pointer-events:auto when cursor is on the popover (so user can click the PubMed link)
  • Positioned below the citation number, shifted left if near right edge of viewport
  • Falls back gracefully if claim/excerpt are absent — shows just title + journal

JavaScript Implementation

Replace the current ~20-line mouseover handler in api.py with:

(function() {
    const popover = document.createElement('div');
    popover.id = 'cite-popover';
    popover.style.cssText = 'position:fixed;display:none;background:#1a1a2e;color:#e0e0e0;' +
        'border:1px solid rgba(79,195,247,0.35);border-radius:8px;padding:0.75rem 1rem;' +
        'font-size:0.82rem;max-width:380px;z-index:10000;pointer-events:none;' +
        'box-shadow:0 6px 20px rgba(0,0,0,0.5);line-height:1.5;opacity:0;' +
        'transition:opacity 0.12s ease;';
    document.body.appendChild(popover);

    let showTimer = null, hideTimer = null, currentKey = null;

    function showPopover(a, key) {
        if (key === currentKey) return;
        currentKey = key;
        const r = refs[key] || {};
        const num = a.textContent.replace(/[\[\]]/g,'');
        const url = r.pmid ? 'https://pubmed.ncbi.nlm.nih.gov/' + r.pmid + '/'
                   : r.doi ? 'https://doi.org/' + r.doi : '';

        let html = '<div style="font-weight:600;color:#4fc3f7;margin-bottom:0.3rem">[' + num + '] ';
        if (r.authors) html += r.authors.split(',')[0] + ' et al.';
        if (r.year) html += ' (' + r.year + ')';
        if (r.journal) html += ' &middot; <span style="color:#888;font-weight:400">' + r.journal + '</span>';
        html += '</div>';

        if (r.title) {
            const clean = r.title.replace(/^["']|["']$/g,'');
            html += '<div style="color:#e0e0e0;font-style:italic;margin-bottom:0.4rem;font-size:0.8rem">' + clean + '</div>';
        }
        if (r.claim) {
            html += '<div style="background:rgba(129,199,132,0.1);border-left:2px solid #81c784;' +
                'padding:0.3rem 0.5rem;margin-bottom:0.4rem;font-size:0.78rem;color:#c8e6c9">' +
                '&#x21B3; ' + r.claim + '</div>';
        }
        if (r.excerpt) {
            html += '<div style="color:#aaa;font-size:0.75rem;font-style:italic;margin-bottom:0.4rem">&ldquo;' +
                r.excerpt + '&rdquo;';
            if (r.figure_ref) html += ' <span style="color:#ffd54f">— ' + r.figure_ref + '</span>';
            html += '</div>';
        }
        if (url) {
            html += '<div style="display:flex;justify-content:flex-end;margin-top:0.3rem">' +
                '<a href="' + url + '" target="_blank" rel="noopener" ' +
                'style="color:#4fc3f7;font-size:0.75rem;pointer-events:auto;text-decoration:none">' +
                (r.pmid ? 'PubMed' : 'DOI') + ' &#x2192;</a></div>';
        }

        popover.innerHTML = html;
        popover.style.pointerEvents = url ? 'auto' : 'none';

        const rect = a.getBoundingClientRect();
        popover.style.display = 'block';
        const pw = popover.offsetWidth;
        const left = Math.max(8, Math.min(rect.left, window.innerWidth - pw - 8));
        const top = rect.bottom + 6;
        popover.style.left = left + 'px';
        popover.style.top = (top + window.scrollY) + 'px';
        popover.style.position = 'absolute';
        popover.style.opacity = '1';
    }

    function hidePopover() {
        currentKey = null;
        popover.style.opacity = '0';
        setTimeout(() => { popover.style.display = 'none'; }, 120);
    }

    contentEl.addEventListener('mouseover', function(e) {
        const a = e.target.closest('a.ref-link');
        if (!a) return;
        const key = a.getAttribute('data-ref-key');
        if (!key || !refs[key]) return;
        clearTimeout(hideTimer);
        showTimer = setTimeout(() => showPopover(a, key), 150);
    });

    contentEl.addEventListener('mouseout', function(e) {
        const a = e.target.closest('a.ref-link');
        if (!a) return;
        clearTimeout(showTimer);
        hideTimer = setTimeout(hidePopover, 300);
    });

    popover.addEventListener('mouseover', () => clearTimeout(hideTimer));
    popover.addEventListener('mouseout', () => { hideTimer = setTimeout(hidePopover, 300); });
})();

data-ref-key Attribute

The citation superscript builder must also set data-ref-key on the <a> element (in addition to existing data-ref-text):

// In the [@key] replacement block:
return '<sup class="cite-sup" ...>'
    + '<a href="#ref-' + num + '" class="ref-link" '
    + 'data-ref-key="' + key + '" '           // <-- ADD THIS
    + 'data-ref-text="' + tip + '" '
    + 'title="' + tip + '">'
    + '[' + num + ']</a></sup>';

Acceptance Criteria

☐ [To be defined]

Testing

After implementing:

  • Visit /wiki/genes-foxp1 — hover over any [@bacon2020] citation → rich popover appears
  • Popover shows: authors, year, journal, claim, excerpt (if set in refs_json)
  • PubMed link in popover is clickable and opens correct paper
  • Popover dismisses after moving cursor away
  • Works on mobile (touch): tap citation → popover appears, tap elsewhere → dismisses
  • Work Log

    2026-04-10 — Slot 0

    • Status: done (small fix)
    • Issue: pointer-events was set conditionally (url ? 'auto' : 'none') which meant the popover body had pointer-events:none when there was no DOI/PMID, making the PubMed link inside unclickable even when present
    • Fix: Changed to unconditional popover.style.pointerEvents = 'auto' so the popover is always interactive
    • Tested: API /api/status returns 200, service healthy
    • Committed and pushed: 3599f08f

    Notes

    • Do NOT break existing [@key][N] rendering — only change the hover behavior
    • The refs_json schema enrichment (claim, excerpt, figure_ref) is a separate task; the popover should degrade gracefully when those fields are absent
    • CSS pointer-events:auto on the popover allows clicking the PubMed link

    Tasks using this spec (1)
    [Atlas] Rich hover popover for wiki citations — claim + exce
    Atlas done P88
    File: wiki-citation-popover-spec.md
    Modified: 2026-04-28 03:24
    Size: 8.1 KB