diff --git a/extension/sidepanel.css b/extension/sidepanel.css
index 9f33ee28..8c01d41e 100644
--- a/extension/sidepanel.css
+++ b/extension/sidepanel.css
@@ -590,6 +590,59 @@ body::after {
}
/* ─── Command Bar ─────────────────────────────────────── */
+/* ─── Quick Actions Toolbar ─────────────────────────────── */
+
+.quick-actions {
+ display: flex;
+ gap: 6px;
+ padding: 4px 8px;
+ background: var(--bg-surface);
+ border-top: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+
+.quick-action-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 26px;
+ padding: 0 10px;
+ background: none;
+ border: 1px solid var(--zinc-600);
+ border-radius: var(--radius-sm);
+ color: var(--text-label);
+ font-family: var(--font-system);
+ font-size: 11px;
+ cursor: pointer;
+ transition: all 150ms;
+}
+
+.quick-action-btn:hover {
+ background: rgba(255, 255, 255, 0.05);
+ color: var(--text-body);
+ border-color: var(--zinc-400);
+}
+
+.quick-action-btn:active {
+ transform: scale(0.96);
+}
+
+.quick-action-btn.loading {
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+.quick-action-btn.loading::after {
+ content: '';
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border: 2px solid var(--zinc-600);
+ border-top-color: var(--amber-400);
+ border-radius: 50%;
+ animation: spin 0.6s linear infinite;
+}
+
.command-bar {
display: flex;
align-items: center;
diff --git a/extension/sidepanel.html b/extension/sidepanel.html
index 5d82c2b8..c51f7df2 100644
--- a/extension/sidepanel.html
+++ b/extension/sidepanel.html
@@ -136,6 +136,12 @@
Browser co-pilot — controls this browser, reports back to your workspace
+
+
+
+
+
+
diff --git a/extension/sidepanel.js b/extension/sidepanel.js
index f1365c88..81438ff2 100644
--- a/extension/sidepanel.js
+++ b/extension/sidepanel.js
@@ -1149,73 +1149,74 @@ inspectorSendBtn.addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'sidebar-command', message });
});
-// ─── Cleanup Button ─────────────────────────────────────────────
+// ─── Quick Action Helpers (shared between chat toolbar + inspector) ──
+
+async function runCleanup(...buttons) {
+ if (!serverUrl || !serverToken) {
+ addChatEntry({ type: 'notification', message: 'Not connected to browse server' });
+ return;
+ }
+ buttons.forEach(b => b?.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' });
+ if (typeof inspectorShowEmpty === 'function') 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 {
+ buttons.forEach(b => b?.classList.remove('loading'));
+ }
+}
+
+async function runScreenshot(...buttons) {
+ if (!serverUrl || !serverToken) {
+ addChatEntry({ type: 'notification', message: 'Not connected to browse server' });
+ return;
+ }
+ buttons.forEach(b => b?.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 {
+ buttons.forEach(b => b?.classList.remove('loading'));
+ }
+}
+
+// ─── Wire up all cleanup/screenshot buttons (inspector + chat toolbar) ──
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');
- }
- });
-}
+const chatCleanupBtn = document.getElementById('chat-cleanup-btn');
+const chatScreenshotBtn = document.getElementById('chat-screenshot-btn');
+
+if (inspectorCleanupBtn) inspectorCleanupBtn.addEventListener('click', () => runCleanup(inspectorCleanupBtn, chatCleanupBtn));
+if (inspectorScreenshotBtn) inspectorScreenshotBtn.addEventListener('click', () => runScreenshot(inspectorScreenshotBtn, chatScreenshotBtn));
+if (chatCleanupBtn) chatCleanupBtn.addEventListener('click', () => runCleanup(chatCleanupBtn, inspectorCleanupBtn));
+if (chatScreenshotBtn) chatScreenshotBtn.addEventListener('click', () => runScreenshot(chatScreenshotBtn, inspectorScreenshotBtn));
// ─── Section Toggles ────────────────────────────────────────────