mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-17 17:51:27 +08:00
feat: Chrome extension Side Panel + Conductor API proposal
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>
This commit is contained in:
96
extension/content.js
Normal file
96
extension/content.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user