From 7e31568c7887a14056fcdd2c5ae5b2f637baa9f4 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 29 Mar 2026 23:11:20 -0700 Subject: [PATCH] feat: cleanup + screenshot buttons in sidebar inspector toolbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two action buttons in the inspector toolbar: - Cleanup (๐Ÿงน): POSTs cleanup --all to server, shows spinner, chat notification on success, resets inspector state (element may be removed) - Screenshot (๐Ÿ“ธ): POSTs screenshot to server, shows spinner, chat notification with saved file path Shared infrastructure: - .inspector-action-btn CSS with loading spinner via ::after pseudo-element - chat-notification type in addChatEntry() for system messages - package.json version bump to 0.13.9.0 Co-Authored-By: Claude Opus 4.6 (1M context) --- extension/sidepanel.css | 57 +++++++++++++++++++++++++++++ extension/sidepanel.html | 3 ++ extension/sidepanel.js | 78 ++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 4 files changed, 139 insertions(+), 1 deletion(-) diff --git a/extension/sidepanel.css b/extension/sidepanel.css index bb53efa8..9f33ee28 100644 --- a/extension/sidepanel.css +++ b/extension/sidepanel.css @@ -221,6 +221,13 @@ body::after { color: #000; border-bottom-right-radius: var(--radius-sm); } +.chat-notification { + text-align: center; + font-size: 11px; + color: var(--text-meta); + padding: 4px 12px; + font-family: var(--font-mono); +} .chat-bubble.assistant { align-self: flex-start; background: var(--bg-surface); @@ -808,6 +815,56 @@ footer { line-height: 1; } +/* โ”€โ”€โ”€ Action Buttons (Cleanup, Screenshot) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + +.inspector-action-btn { + display: flex; + align-items: center; + justify-content: center; + height: 28px; + width: 28px; + padding: 0; + background: none; + border: 1px solid var(--zinc-600); + border-radius: var(--radius-sm); + color: var(--text-label); + font-size: 14px; + cursor: pointer; + transition: all 150ms; + flex-shrink: 0; +} + +.inspector-action-btn:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--text-body); + border-color: var(--zinc-400); +} + +.inspector-action-btn:active { + transform: scale(0.95); +} + +.inspector-action-btn.loading { + pointer-events: none; + opacity: 0.5; + position: relative; +} + +.inspector-action-btn.loading::after { + content: ''; + position: absolute; + width: 12px; + height: 12px; + border: 2px solid var(--zinc-600); + border-top-color: var(--amber-400); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + .inspector-selected { font-family: var(--font-mono); font-size: 11px; diff --git a/extension/sidepanel.html b/extension/sidepanel.html index 5fe73070..5d82c2b8 100644 --- a/extension/sidepanel.html +++ b/extension/sidepanel.html @@ -60,6 +60,9 @@ +
+ + diff --git a/extension/sidepanel.js b/extension/sidepanel.js index 1ff287f7..f1365c88 100644 --- a/extension/sidepanel.js +++ b/extension/sidepanel.js @@ -135,6 +135,16 @@ function addChatEntry(entry) { return; } + // System notifications (cleanup, screenshot, errors) + if (entry.type === 'notification') { + const note = document.createElement('div'); + note.className = 'chat-notification'; + note.textContent = entry.message; + chatMessages.appendChild(note); + note.scrollIntoView({ behavior: 'smooth', block: 'end' }); + return; + } + // Agent streaming events if (entry.role === 'agent') { handleAgentEvent(entry); @@ -1139,6 +1149,74 @@ inspectorSendBtn.addEventListener('click', () => { chrome.runtime.sendMessage({ type: 'sidebar-command', message }); }); +// โ”€โ”€โ”€ Cleanup Button โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +const inspectorCleanupBtn = document.getElementById('inspector-cleanup-btn'); +if (inspectorCleanupBtn) { + inspectorCleanupBtn.addEventListener('click', async () => { + if (inspectorCleanupBtn.classList.contains('loading')) return; + if (!serverUrl || !serverToken) { + addChatEntry({ type: 'notification', message: 'Not connected to browse server' }); + return; + } + inspectorCleanupBtn.classList.add('loading'); + try { + const resp = await fetch(`${serverUrl}/command`, { + method: 'POST', + headers: { ...authHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify({ command: 'cleanup', args: ['--all'] }), + signal: AbortSignal.timeout(15000), + }); + const text = await resp.text(); + if (resp.ok) { + addChatEntry({ type: 'notification', message: text || 'Page cleaned up' }); + // Reset inspector โ€” selected element may have been removed + inspectorShowEmpty(); + } else { + const err = JSON.parse(text).error || 'Cleanup failed'; + addChatEntry({ type: 'notification', message: 'Error: ' + err }); + } + } catch (err) { + addChatEntry({ type: 'notification', message: 'Cleanup failed: ' + err.message }); + } finally { + inspectorCleanupBtn.classList.remove('loading'); + } + }); +} + +// โ”€โ”€โ”€ Screenshot Button โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +const inspectorScreenshotBtn = document.getElementById('inspector-screenshot-btn'); +if (inspectorScreenshotBtn) { + inspectorScreenshotBtn.addEventListener('click', async () => { + if (inspectorScreenshotBtn.classList.contains('loading')) return; + if (!serverUrl || !serverToken) { + addChatEntry({ type: 'notification', message: 'Not connected to browse server' }); + return; + } + inspectorScreenshotBtn.classList.add('loading'); + try { + const resp = await fetch(`${serverUrl}/command`, { + method: 'POST', + headers: { ...authHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify({ command: 'screenshot', args: [] }), + signal: AbortSignal.timeout(15000), + }); + const text = await resp.text(); + if (resp.ok) { + addChatEntry({ type: 'notification', message: text || 'Screenshot saved' }); + } else { + const err = JSON.parse(text).error || 'Screenshot failed'; + addChatEntry({ type: 'notification', message: 'Error: ' + err }); + } + } catch (err) { + addChatEntry({ type: 'notification', message: 'Screenshot failed: ' + err.message }); + } finally { + inspectorScreenshotBtn.classList.remove('loading'); + } + }); +} + // โ”€โ”€โ”€ Section Toggles โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ document.querySelectorAll('.inspector-section-toggle').forEach(toggle => { diff --git a/package.json b/package.json index 13b85f96..f34218c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "0.13.8.0", + "version": "0.13.9.0", "description": "Garry's Stack โ€” Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module",