mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 21:25:27 +02:00
f62377748b
Small pill in bottom-right corner of every page: "● gstack · 3 refs" Shows when connected via CDP, fades to 30% opacity after 3s, full on hover. Disappears entirely when disconnected. Background worker now notifies content scripts on connect/disconnect state changes so the pill appears/disappears without polling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
146 lines
4.2 KiB
JavaScript
146 lines
4.2 KiB
JavaScript
/**
|
|
* gstack browse — content script
|
|
*
|
|
* Receives ref data from background worker via chrome.runtime.onMessage.
|
|
* Renders @ref overlay badges on the page (CDP mode only — positions are accurate).
|
|
* In headless mode, shows a floating ref panel instead (positions unknown).
|
|
*/
|
|
|
|
let overlayContainer = null;
|
|
let statusPill = null;
|
|
let pillFadeTimer = null;
|
|
let refCount = 0;
|
|
|
|
// ─── Connection Status Pill ──────────────────────────────────
|
|
|
|
function showStatusPill(connected, refs) {
|
|
refCount = refs || 0;
|
|
|
|
if (!statusPill) {
|
|
statusPill = document.createElement('div');
|
|
statusPill.id = 'gstack-status-pill';
|
|
document.body.appendChild(statusPill);
|
|
}
|
|
|
|
if (!connected) {
|
|
statusPill.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
const refText = refCount > 0 ? ` · ${refCount} refs` : '';
|
|
statusPill.innerHTML = `<span class="gstack-pill-dot"></span> gstack${refText}`;
|
|
statusPill.style.display = 'flex';
|
|
statusPill.style.opacity = '1';
|
|
|
|
// Fade to subtle after 3s
|
|
clearTimeout(pillFadeTimer);
|
|
pillFadeTimer = setTimeout(() => {
|
|
statusPill.style.opacity = '0.3';
|
|
}, 3000);
|
|
}
|
|
|
|
function hideStatusPill() {
|
|
if (statusPill) {
|
|
statusPill.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function ensureContainer() {
|
|
if (overlayContainer) return overlayContainer;
|
|
overlayContainer = document.createElement('div');
|
|
overlayContainer.id = 'gstack-ref-overlays';
|
|
overlayContainer.style.cssText = 'position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;';
|
|
document.body.appendChild(overlayContainer);
|
|
return overlayContainer;
|
|
}
|
|
|
|
function clearOverlays() {
|
|
if (overlayContainer) {
|
|
overlayContainer.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
function renderRefBadges(refs) {
|
|
clearOverlays();
|
|
if (!refs || refs.length === 0) return;
|
|
|
|
const container = ensureContainer();
|
|
|
|
for (const ref of refs) {
|
|
// Try to find the element using accessible name/role for positioning
|
|
// In CDP mode, we could use bounding boxes from the server
|
|
// For now, use a floating panel approach
|
|
const badge = document.createElement('div');
|
|
badge.className = 'gstack-ref-badge';
|
|
badge.textContent = ref.ref;
|
|
badge.title = `${ref.role}: "${ref.name}"`;
|
|
container.appendChild(badge);
|
|
}
|
|
}
|
|
|
|
function renderRefPanel(refs) {
|
|
clearOverlays();
|
|
if (!refs || refs.length === 0) return;
|
|
|
|
const container = ensureContainer();
|
|
|
|
const panel = document.createElement('div');
|
|
panel.className = 'gstack-ref-panel';
|
|
|
|
const header = document.createElement('div');
|
|
header.className = 'gstack-ref-panel-header';
|
|
header.textContent = `gstack refs (${refs.length})`;
|
|
header.style.cssText = 'pointer-events: auto; cursor: move;';
|
|
panel.appendChild(header);
|
|
|
|
const list = document.createElement('div');
|
|
list.className = 'gstack-ref-panel-list';
|
|
for (const ref of refs.slice(0, 30)) { // Show max 30 in panel
|
|
const row = document.createElement('div');
|
|
row.className = 'gstack-ref-panel-row';
|
|
row.innerHTML = `<span class="gstack-ref-panel-id">${ref.ref}</span> <span class="gstack-ref-panel-role">${ref.role}</span> <span class="gstack-ref-panel-name">"${ref.name}"</span>`;
|
|
list.appendChild(row);
|
|
}
|
|
if (refs.length > 30) {
|
|
const more = document.createElement('div');
|
|
more.className = 'gstack-ref-panel-more';
|
|
more.textContent = `+${refs.length - 30} more`;
|
|
list.appendChild(more);
|
|
}
|
|
panel.appendChild(list);
|
|
container.appendChild(panel);
|
|
}
|
|
|
|
// Listen for messages from background worker
|
|
chrome.runtime.onMessage.addListener((msg) => {
|
|
if (msg.type === 'refs' && msg.data) {
|
|
const refs = msg.data.refs || [];
|
|
const mode = msg.data.mode;
|
|
|
|
if (refs.length === 0) {
|
|
clearOverlays();
|
|
showStatusPill(true, 0);
|
|
return;
|
|
}
|
|
|
|
// CDP mode: could use bounding boxes (future)
|
|
// For now: floating panel for all modes
|
|
renderRefPanel(refs);
|
|
showStatusPill(true, refs.length);
|
|
}
|
|
|
|
if (msg.type === 'clearRefs') {
|
|
clearOverlays();
|
|
showStatusPill(true, 0);
|
|
}
|
|
|
|
if (msg.type === 'connected') {
|
|
showStatusPill(true, refCount);
|
|
}
|
|
|
|
if (msg.type === 'disconnected') {
|
|
hideStatusPill();
|
|
clearOverlays();
|
|
}
|
|
});
|