mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 13:45:35 +02:00
410d0abd9b
Chrome extension (Manifest V3, sideload): - Side Panel with live activity feed, @ref overlays, dark terminal aesthetic - Background worker: health polling, SSE relay, ref fetching - Popup: port config, connection status, side panel launcher - Content script: floating ref panel with @ref badges Conductor API proposal (docs/designs/CONDUCTOR_SESSION_API.md): - SSE endpoint for full Claude Code session mirroring in Side Panel - Discovery via HTTP endpoint (not filesystem — extensions can't read files) TODOS.md: add $B watch, multi-agent tabs, cross-platform CDP, Web Store publishing. Mark CDP mode as shipped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
3.0 KiB
JavaScript
97 lines
3.0 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;
|
|
|
|
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 ref data 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();
|
|
return;
|
|
}
|
|
|
|
// CDP mode: could use bounding boxes (future)
|
|
// For now: floating panel for all modes
|
|
renderRefPanel(refs);
|
|
}
|
|
|
|
if (msg.type === 'clearRefs') {
|
|
clearOverlays();
|
|
}
|
|
});
|