Skip to content

Add Vercel-style developer tools browser toolbar - #6

Merged
vercel[bot] merged 1 commit into
devfrom
v0/vercel-toolbar-extension
Oct 8, 2026
Merged

vercel[bot] merged 1 commit into
devfrom
v0/vercel-toolbar-extension

Conversation

@khulnasoft-bot

@khulnasoft-bot khulnasoft-bot commented Oct 8, 2026 •

Copy link
Copy Markdown
  • Implemented a browser-based developer tools toolbar featuring Vercel's signature design system
  • Added a persistent UI component to provide quick access to developer utilities and environment information

v0 Session

Summary by Sourcery

Redesign the browser developer toolbar to provide a polished, searchable, and persistent developer utility experience.

New Features:

  • Provide a redesigned persistent developer toolbar with quick access to inspection, screenshots, search, settings, and developer utilities.
  • Add searchable categorized access to popular tools, including JWT decoding, from the toolbar menu.
  • Support keyboard shortcuts, draggable positioning, and persisted toolbar visibility and location.

Enhancements:

  • Refresh the toolbar interface with a compact Vercel-inspired visual design, improved accessibility labeling, responsive menu behavior, and cross-browser extension API handling.

@vercel vercel Bot added the v0 label Oct 8, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR redesigns the extension toolbar as a compact Vercel-style developer interface, generates a searchable categorized tool menu, and modernizes actions, keyboard controls, drag positioning, and browser-storage persistence across both source and built extension assets.

Sequence diagram for toolbar actions and tool discovery

sequenceDiagram
    actor User
    participant Toolbar
    participant Storage as BrowserStorage
    participant Runtime as ExtensionRuntime
    participant Tools as DevToolsSite

    User->>Toolbar: click menuBtn
    Toolbar->>Toolbar: renderTools(query)
    User->>Toolbar: enter search query
    Toolbar->>Toolbar: renderTools(query)
    User->>Toolbar: select tool item
    Toolbar->>Tools: openTool(path)
    User->>Toolbar: click inspectBtn or screenshotBtn
    Toolbar->>Runtime: sendMessage(action)
    User->>Toolbar: click hideBtn or press Escape
    Toolbar->>Storage: set(toolbarHidden: true)
Loading

File-Level Changes

Change Details Files
Replaced the toolbar presentation with a compact Vercel-inspired dark interface and improved accessibility semantics.
  • Reduced and consolidated the CSS for the floating toolbar, buttons, tooltips, menu, search field, and responsive scrolling.
  • Added inline SVG icons, ARIA labels, button types, expanded-state tracking, descriptions, and empty-search feedback.
  • Applied the same UI redesign to the source and built extension HTML files.
extension/toolbar.html
dist-extension/toolbar.html
Reworked tool discovery and interaction around a data-driven, searchable menu.
  • Defined categorized quick actions, popular tools, settings, and a new JWT Decoder entry in a single tools model.
  • Rendered menu sections dynamically and filtered items by label and description.
  • Added menu actions for inspection, screenshots, preferences, hiding, and opening external developer utilities.
extension/toolbar.js
dist-extension/toolbar.js
Updated toolbar state, keyboard controls, persistence, and browser API integration.
  • Unified Firefox/Chrome runtime and storage access through a browserAPI adapter.
  • Persisted hidden state and dragged position in extension storage with viewport-constrained dragging.
  • Added Ctrl/Cmd+K, slash, and Escape handling while removing the prior configurable shortcut and restore-indicator implementation.
extension/toolbar.js
dist-extension/toolbar.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
enclosed Error Error Oct 8, 2026 2:49am UTC

@vercel
vercel Bot merged commit dd0cfc5 into dev Oct 8, 2026
2 of 11 checks passed
@vercel
vercel Bot deleted the v0/vercel-toolbar-extension branch October 8, 2026 02:49
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fd6e659b-730c-4e54-ad8e-48515c216860
📥 Commits

Reviewing files that changed from the base of the PR and between d4b33c6 and dd933cd.

📒 Files selected for processing (4)
  • dist-extension/toolbar.html
  • dist-extension/toolbar.js
  • extension/toolbar.html
  • extension/toolbar.js
 __________________________________________________________________________________
< This is the beginning of a beautiful friendship...between you and bug-free code. >
 ----------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 security issues, and 15 other issues

Security issues:

  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a toolSections.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a toolSections.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="extension/toolbar.html" line_range="45" />
<code_context>
+  <aside class="it-toolbar" id="toolbar" aria-label="DevTools toolbar">
+    <span class="status-indicator" id="statusIndicator" aria-label="Toolbar active"></span>
+    <span class="drag-handle" id="dragHandle" aria-hidden="true"></span>
+    <button class="toolbar-btn" id="menuBtn" type="button" aria-label="Open DevTools menu" aria-expanded="false">
+      <svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></svg><span class="tooltip">Tools</span>
     </button>
</code_context>
<issue_to_address>
**Toolbar controls never receive clicks**

The content script sets the containing iframe’s `pointer-events` to `none` in `extension/content-script.js:177-185`, so clicks pass through the iframe instead of reaching these buttons. The toolbar appears, but its menu, search, inspect, screenshot, and hide controls cannot be used.

Allow pointer events on the toolbar iframe while keeping the iframe document background transparent or otherwise preventing it from blocking the page.
</issue_to_address>

### Comment 2
<location path="extension/toolbar.js" line_range="48" />
<code_context>
+function sendMessage(action) { browserAPI.runtime.sendMessage({ action }).catch(() => {}); }
+function openMenu(focus = false) { isMenuOpen = true; menuPanel.classList.add('open'); menuBtn.classList.add('active'); menuBtn.setAttribute('aria-expanded', 'true'); if (focus) menuSearch.focus(); }
+function closeMenu() { isMenuOpen = false; menuPanel.classList.remove('open'); menuBtn.classList.remove('active'); menuBtn.setAttribute('aria-expanded', 'false'); menuSearch.value = ''; renderTools(); }
+function hideToolbar() { toolbar.style.display = 'none'; browserAPI.storage.local.set({ toolbarHidden: true }); }
+function wakeUp() { isSleeping = false; toolbar.classList.remove('sleeping'); statusIndicator.classList.remove('sleeping'); }
+function sleep() { isSleeping = true; toolbar.classList.add('sleeping'); statusIndicator.classList.add('sleeping'); }
</code_context>
<issue_to_address>
**Toolbar stays hidden everywhere**

When the user hides the toolbar, `hideToolbar()` persists `toolbarHidden: true`, and startup hides every later toolbar instance when it reads that value. Nothing clears the flag or restores the toolbar, so it remains unavailable on subsequent pages.

Scope the hidden state to the current page or provide a restore action that clears the stored flag and shows the toolbar again.

Also at `extension/toolbar.js:27`, `extension/toolbar.js:86-87`.
</issue_to_address>

### Comment 3
<location path="extension/toolbar.js" line_range="85-86" />
<code_context>
+document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; const rect = toolbar.getBoundingClientRect(); browserAPI.storage.local.set({ toolbarPosition: { x: rect.left, y: rect.top } }); });
+
+browserAPI.storage.local.get(['toolbarPosition', 'toolbarHidden'], (result) => {
+  if (result.toolbarPosition) { toolbar.style.right = 'auto'; toolbar.style.transform = 'none'; toolbar.style.left = `${result.toolbarPosition.x}px`; toolbar.style.top = `${result.toolbarPosition.y}px`; }
+  if (result.toolbarHidden) toolbar.style.display = 'none';
+});
</code_context>
<issue_to_address>
**Saved state fails in Firefox**

When the toolbar runs in Firefox, `browserAPI.storage.local.get` receives a callback, but Firefox’s native `browser.*` storage API is promise-based and rejects that argument. The saved position and hidden state are not applied.

Read the storage result using the promise-based API supported by Firefox.

Also at `extension/toolbar.js:87`.
</issue_to_address>

### Comment 4
<location path="extension/toolbar.js" line_range="56" />
<code_context>
+renderTools();
+menuBtn.addEventListener('click', () => (isMenuOpen ? closeMenu() : openMenu()));
+document.getElementById('searchBtn').addEventListener('click', () => openMenu(true));
+document.getElementById('inspectBtn').addEventListener('click', () => sendMessage('inspectElement'));
+document.getElementById('screenshotBtn').addEventListener('click', () => sendMessage('takeScreenshot'));
+document.getElementById('hideBtn').addEventListener('click', hideToolbar);
</code_context>
<issue_to_address>
**Inspect never activates**

When the user selects Inspect from the toolbar, `sendMessage('inspectElement')` sends to extension runtime listeners, not the page’s content script, and the background does not forward the action. The content script’s inspection handler never runs, so the user cannot inspect elements.

Forward the inspect action to the page’s content script and handle it there.

Also at `extension/toolbar.js:66`.
</issue_to_address>

### Comment 5
<location path="extension/toolbar.js" line_range="57" />
<code_context>
+menuBtn.addEventListener('click', () => (isMenuOpen ? closeMenu() : openMenu()));
+document.getElementById('searchBtn').addEventListener('click', () => openMenu(true));
+document.getElementById('inspectBtn').addEventListener('click', () => sendMessage('inspectElement'));
+document.getElementById('screenshotBtn').addEventListener('click', () => sendMessage('takeScreenshot'));
+document.getElementById('hideBtn').addEventListener('click', hideToolbar);
+menuSearch.addEventListener('input', (event) => renderTools(event.target.value));
</code_context>
<issue_to_address>
**Screenshot is never delivered**

When the user activates the screenshot action and capture succeeds, `sendMessage('takeScreenshot')` discards the response containing the screenshot data URL. The toolbar does not download or display the image, so the user gets no screenshot.

Handle the screenshot response and download or display its data URL.

Also at `extension/toolbar.js:56`, `extension/toolbar.js:58`.
</issue_to_address>

### Comment 6
<location path="extension/toolbar.js" line_range="68" />
<code_context>
+  if (id === 'inspect') return sendMessage('inspectElement');
+  if (id === 'screenshot') return sendMessage('takeScreenshot');
+  if (id === 'hide') return hideToolbar();
+  if (id === 'preferences') return browserAPI.runtime.sendMessage({ action: 'openOptions' });
+  if (item.dataset.path !== '') openTool(item.dataset.path);
+  else openTool('');
</code_context>
<issue_to_address>
**Preferences never open**

When the user selects Preferences, the toolbar sends `openOptions`, but no background handler opens the options page, and the manifest does not register one. The user remains on the current page instead of reaching settings.

Register the options page and open it through a supported options-page path.
</issue_to_address>

### Comment 7
<location path="extension/toolbar.js" line_range="75-77" />
<code_context>
+document.addEventListener('click', (event) => { if (!toolbar.contains(event.target)) closeMenu(); });
+document.addEventListener('keydown', (event) => {
+  const key = event.key.toLowerCase();
+  if ((event.ctrlKey || event.metaKey) && key === 'k') { event.preventDefault(); openMenu(true); }
+  else if (key === '/' && document.activeElement !== menuSearch) { event.preventDefault(); openMenu(true); }
+  else if (key === 'escape') { if (isMenuOpen) closeMenu(); else hideToolbar(); }
+});
+
</code_context>
<issue_to_address>
**Saved shortcuts do not work**

When the user changes shortcuts in Options, the key handler recognizes only hard-coded Ctrl/Meta+K, `/`, and Escape, and does not load the saved shortcut map or handle configured tool shortcuts. The menu also shows fixed shortcut labels, so custom bindings do not work and hints can be wrong.

Load the saved shortcut map and use it both to handle toolbar actions and render shortcut labels.

Also at `extension/toolbar.js:11-27`.
</issue_to_address>

### Comment 8
<location path="extension/toolbar.js" line_range="66" />
<code_context>
+  const id = item.dataset.id;
+  if (id === 'search') return openMenu(true);
+  if (id === 'inspect') return sendMessage('inspectElement');
+  if (id === 'screenshot') return sendMessage('takeScreenshot');
+  if (id === 'hide') return hideToolbar();
+  if (id === 'preferences') return browserAPI.runtime.sendMessage({ action: 'openOptions' });
</code_context>
<issue_to_address>
**Menu screenshot includes the open menu**

When a user chooses Capture screenshot from the open menu, the screenshot menu action sends `takeScreenshot` without closing the menu, and the background captures the current visible tab. The captured image therefore includes the toolbar menu covering the page instead of just the unobstructed view.

Close the menu before sending the screenshot request.
</issue_to_address>

### Comment 9
<location path="extension/toolbar.js" line_range="85" />
<code_context>
+document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; const rect = toolbar.getBoundingClientRect(); browserAPI.storage.local.set({ toolbarPosition: { x: rect.left, y: rect.top } }); });
+
+browserAPI.storage.local.get(['toolbarPosition', 'toolbarHidden'], (result) => {
+  if (result.toolbarPosition) { toolbar.style.right = 'auto'; toolbar.style.transform = 'none'; toolbar.style.left = `${result.toolbarPosition.x}px`; toolbar.style.top = `${result.toolbarPosition.y}px`; }
+  if (result.toolbarHidden) toolbar.style.display = 'none';
+});
</code_context>
<issue_to_address>
**Toolbar can be offscreen**

When a saved toolbar position is restored in a smaller viewport, startup applies saved coordinates without checking the current viewport. The toolbar can end up partly or entirely offscreen, leaving the user unable to reach or drag it back.

Clamp the restored coordinates to the current viewport before applying them.

Also at `extension/toolbar.js:82`.
</issue_to_address>

### Comment 10
<location path="extension/toolbar.js" line_range="85" />
<code_context>
+document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; const rect = toolbar.getBoundingClientRect(); browserAPI.storage.local.set({ toolbarPosition: { x: rect.left, y: rect.top } }); });
+
+browserAPI.storage.local.get(['toolbarPosition', 'toolbarHidden'], (result) => {
+  if (result.toolbarPosition) { toolbar.style.right = 'auto'; toolbar.style.transform = 'none'; toolbar.style.left = `${result.toolbarPosition.x}px`; toolbar.style.top = `${result.toolbarPosition.y}px`; }
+  if (result.toolbarHidden) toolbar.style.display = 'none';
+});
</code_context>
<issue_to_address>
**Startup overrides a new position**

When the user drags the toolbar before the initial storage read completes, the storage callback applies the older saved coordinates after the drag has saved and displayed a new position. The toolbar jumps back to the stale coordinates.

Prevent the initial position read from overriding a position changed by a drag.

Also at `extension/toolbar.js:82`.
</issue_to_address>

### Comment 11
<location path="extension/toolbar.js" line_range="85" />
<code_context>
+document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; const rect = toolbar.getBoundingClientRect(); browserAPI.storage.local.set({ toolbarPosition: { x: rect.left, y: rect.top } }); });
+
+browserAPI.storage.local.get(['toolbarPosition', 'toolbarHidden'], (result) => {
+  if (result.toolbarPosition) { toolbar.style.right = 'auto'; toolbar.style.transform = 'none'; toolbar.style.left = `${result.toolbarPosition.x}px`; toolbar.style.top = `${result.toolbarPosition.y}px`; }
+  if (result.toolbarHidden) toolbar.style.display = 'none';
+});
</code_context>
<issue_to_address>
**Saved placement resets**

When an upgraded installation has a saved `it-toolbar-position` value, initialization reads `toolbarPosition` from extension storage instead of the legacy `it-toolbar-position` local-storage key. Existing users’ saved coordinates are not migrated, so the toolbar returns to its default position.

Read and migrate the legacy saved position when no extension-storage position exists.
</issue_to_address>

### Comment 12
<location path="extension/toolbar.js" line_range="20" />
<code_context>
+    { label: 'Base64 Converter', description: 'Encode and decode Base64', icon: '64', path: 'base64-string-converter' },
+    { label: 'Regex Tester', description: 'Build and test regular expressions', icon: '.*', path: 'regex-tester' },
+    { label: 'Color Converter', description: 'Convert colors between formats', icon: '◉', path: 'color-converter' },
+    { label: 'JWT Decoder', description: 'Inspect JSON Web Tokens', icon: 'jwt', path: 'jwt-decoder' },
+  ]},
+  { category: 'All tools', items: [
</code_context>
<issue_to_address>
**JWT link opens missing page**

When the user selects JWT Decoder, the JWT entry uses the `jwt-decoder` path, but the application registers the tool at `/jwt-parser` and has no redirect for `/jwt-decoder`. The user opens a missing route instead of the JWT tool.

Change the entry’s path to the registered `/jwt-parser` route.
</issue_to_address>

### Comment 13
<location path="extension/toolbar.html" line_range="22" />
<code_context>
+    .menu-panel { position: absolute; right: calc(100% + 9px); top: -1px; width: 318px; max-height: min(560px, calc(100vh - 32px)); overflow: hidden auto; padding: 7px; border: 1px solid var(--border); border-radius: 10px; background: var(--panel); box-shadow: 0 18px 50px rgba(0,0,0,.36); opacity: 0; visibility: hidden; transform: translateX(7px); transition: opacity .16s, transform .16s, visibility .16s; }
</code_context>
<issue_to_address>
**Menu bottom is clipped**

When the menu opens on a viewport too short to fit the panel below the toolbar, the menu is positioned beside a toolbar centered vertically, but its height limit is based on the whole viewport rather than the space below the menu’s top edge. On shorter viewports, the panel extends beyond the iframe’s clipped viewport, leaving its lower entries inaccessible even when the panel is scrolled.

Position the menu above the toolbar when needed, or constrain its height to the available space between its top edge and the viewport bottom.
</issue_to_address>

### Comment 14
<location path="extension/toolbar.js" line_range="11-25" />
<code_context>
+    { id: 'search', label: 'Search all tools', description: 'Find a utility by name', icon: '⌕', shortcut: '/' },
</code_context>
<issue_to_address>
**Search omits most tools**

`renderTools` filters only the hard-coded `tools` array, which contains five utility links and a link to the site rather than the full tool library. Searching for tools outside that subset returns no match despite the “Search all tools” label.

Populate the search menu from the full tool catalog or change the UI to accurately describe the limited list.
</issue_to_address>

### Comment 15
<location path="extension/toolbar.js" line_range="11-25" />
<code_context>
+    { id: 'search', label: 'Search all tools', description: 'Find a utility by name', icon: '⌕', shortcut: '/' },
</code_context>
<issue_to_address>
**Environment information is absent**

The toolbar renders only quick actions, tool links, and settings; it exposes no page or environment information. Users get utility links but not the environment information promised in the description.

Add a toolbar section that gathers and displays the promised page or environment information.
</issue_to_address>

### Comment 16
<location path="dist-extension/toolbar.js" line_range="42" />
<code_context>
  toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 17
<location path="dist-extension/toolbar.js" line_range="42" />
<code_context>
  toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `toolSections.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 18
<location path="extension/toolbar.js" line_range="42" />
<code_context>
  toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 19
<location path="extension/toolbar.js" line_range="42" />
<code_context>
  toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `toolSections.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

Sourcery assessment

Approval pending. 19 findings to address first.

Blocking findings: extension/toolbar.html:45, extension/toolbar.js:48, extension/toolbar.js:86, extension/toolbar.js:56, extension/toolbar.js:57, and 14 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread extension/toolbar.html
<aside class="it-toolbar" id="toolbar" aria-label="DevTools toolbar">
<span class="status-indicator" id="statusIndicator" aria-label="Toolbar active"></span>
<span class="drag-handle" id="dragHandle" aria-hidden="true"></span>
<button class="toolbar-btn" id="menuBtn" type="button" aria-label="Open DevTools menu" aria-expanded="false">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Toolbar controls never receive clicks

The content script sets the containing iframe’s pointer-events to none in extension/content-script.js:177-185, so clicks pass through the iframe instead of reaching these buttons. The toolbar appears, but its menu, search, inspect, screenshot, and hide controls cannot be used.

Allow pointer events on the toolbar iframe while keeping the iframe document background transparent or otherwise preventing it from blocking the page.

Prompt for AI agents
In `extension/toolbar.html` at line 45:

**Toolbar controls never receive clicks**

The content script sets the containing iframe’s `pointer-events` to `none` in `extension/content-script.js:177-185`, so clicks pass through the iframe instead of reaching these buttons. The toolbar appears, but its menu, search, inspect, screenshot, and hide controls cannot be used.

Allow pointer events on the toolbar iframe while keeping the iframe document background transparent or otherwise preventing it from blocking the page.

Comment thread extension/toolbar.js
function sendMessage(action) { browserAPI.runtime.sendMessage({ action }).catch(() => {}); }
function openMenu(focus = false) { isMenuOpen = true; menuPanel.classList.add('open'); menuBtn.classList.add('active'); menuBtn.setAttribute('aria-expanded', 'true'); if (focus) menuSearch.focus(); }
function closeMenu() { isMenuOpen = false; menuPanel.classList.remove('open'); menuBtn.classList.remove('active'); menuBtn.setAttribute('aria-expanded', 'false'); menuSearch.value = ''; renderTools(); }
function hideToolbar() { toolbar.style.display = 'none'; browserAPI.storage.local.set({ toolbarHidden: true }); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Toolbar stays hidden everywhere

When the user hides the toolbar, hideToolbar() persists toolbarHidden: true, and startup hides every later toolbar instance when it reads that value. Nothing clears the flag or restores the toolbar, so it remains unavailable on subsequent pages.

Scope the hidden state to the current page or provide a restore action that clears the stored flag and shows the toolbar again.

Also at extension/toolbar.js:27, extension/toolbar.js:86-87.

Prompt for AI agents
In `extension/toolbar.js` at line 48:

**Toolbar stays hidden everywhere**

When the user hides the toolbar, `hideToolbar()` persists `toolbarHidden: true`, and startup hides every later toolbar instance when it reads that value. Nothing clears the flag or restores the toolbar, so it remains unavailable on subsequent pages.

Scope the hidden state to the current page or provide a restore action that clears the stored flag and shows the toolbar again.

Also at `extension/toolbar.js:27`, `extension/toolbar.js:86-87`.

Comment thread extension/toolbar.js
Comment on lines +85 to +86
if (result.toolbarPosition) { toolbar.style.right = 'auto'; toolbar.style.transform = 'none'; toolbar.style.left = `${result.toolbarPosition.x}px`; toolbar.style.top = `${result.toolbarPosition.y}px`; }
if (result.toolbarHidden) toolbar.style.display = 'none';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Saved state fails in Firefox

When the toolbar runs in Firefox, browserAPI.storage.local.get receives a callback, but Firefox’s native browser.* storage API is promise-based and rejects that argument. The saved position and hidden state are not applied.

Read the storage result using the promise-based API supported by Firefox.

Also at extension/toolbar.js:87.

Prompt for AI agents
In `extension/toolbar.js` at lines 85-86:

**Saved state fails in Firefox**

When the toolbar runs in Firefox, `browserAPI.storage.local.get` receives a callback, but Firefox’s native `browser.*` storage API is promise-based and rejects that argument. The saved position and hidden state are not applied.

Read the storage result using the promise-based API supported by Firefox.

Also at `extension/toolbar.js:87`.

Comment thread extension/toolbar.js
renderTools();
menuBtn.addEventListener('click', () => (isMenuOpen ? closeMenu() : openMenu()));
document.getElementById('searchBtn').addEventListener('click', () => openMenu(true));
document.getElementById('inspectBtn').addEventListener('click', () => sendMessage('inspectElement'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Inspect never activates

When the user selects Inspect from the toolbar, sendMessage('inspectElement') sends to extension runtime listeners, not the page’s content script, and the background does not forward the action. The content script’s inspection handler never runs, so the user cannot inspect elements.

Forward the inspect action to the page’s content script and handle it there.

Also at extension/toolbar.js:66.

Prompt for AI agents
In `extension/toolbar.js` at line 56:

**Inspect never activates**

When the user selects Inspect from the toolbar, `sendMessage('inspectElement')` sends to extension runtime listeners, not the page’s content script, and the background does not forward the action. The content script’s inspection handler never runs, so the user cannot inspect elements.

Forward the inspect action to the page’s content script and handle it there.

Also at `extension/toolbar.js:66`.

Comment thread extension/toolbar.js
menuBtn.addEventListener('click', () => (isMenuOpen ? closeMenu() : openMenu()));
document.getElementById('searchBtn').addEventListener('click', () => openMenu(true));
document.getElementById('inspectBtn').addEventListener('click', () => sendMessage('inspectElement'));
document.getElementById('screenshotBtn').addEventListener('click', () => sendMessage('takeScreenshot'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Screenshot is never delivered

When the user activates the screenshot action and capture succeeds, sendMessage('takeScreenshot') discards the response containing the screenshot data URL. The toolbar does not download or display the image, so the user gets no screenshot.

Handle the screenshot response and download or display its data URL.

Also at extension/toolbar.js:56, extension/toolbar.js:58.

Prompt for AI agents
In `extension/toolbar.js` at line 57:

**Screenshot is never delivered**

When the user activates the screenshot action and capture succeeds, `sendMessage('takeScreenshot')` discards the response containing the screenshot data URL. The toolbar does not download or display the image, so the user gets no screenshot.

Handle the screenshot response and download or display its data URL.

Also at `extension/toolbar.js:56`, `extension/toolbar.js:58`.

Comment thread extension/toolbar.js
Comment on lines +11 to +25
{ id: 'search', label: 'Search all tools', description: 'Find a utility by name', icon: '⌕', shortcut: '/' },
{ id: 'inspect', label: 'Inspect element', description: 'Pick an element on this page', icon: '↗' },
{ id: 'screenshot', label: 'Capture screenshot', description: 'Save the current page view', icon: '▣' },
]},
{ category: 'Popular tools', items: [
{ label: 'JSON Viewer', description: 'Format and explore JSON', icon: '{}' , path: 'json-viewer' },
{ label: 'Base64 Converter', description: 'Encode and decode Base64', icon: '64', path: 'base64-string-converter' },
{ label: 'Regex Tester', description: 'Build and test regular expressions', icon: '.*', path: 'regex-tester' },
{ label: 'Color Converter', description: 'Convert colors between formats', icon: '◉', path: 'color-converter' },
{ label: 'JWT Decoder', description: 'Inspect JSON Web Tokens', icon: 'jwt', path: 'jwt-decoder' },
]},
{ category: 'All tools', items: [
{ label: 'Open DevTools', description: 'Browse the complete tool library', icon: '↗', path: '' },
]},
{ category: 'Settings', items: [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Environment information is absent

The toolbar renders only quick actions, tool links, and settings; it exposes no page or environment information. Users get utility links but not the environment information promised in the description.

Add a toolbar section that gathers and displays the promised page or environment information.

Prompt for AI agents
In `extension/toolbar.js` at lines 11-25:

**Environment information is absent**

The toolbar renders only quick actions, tool links, and settings; it exposes no page or environment information. Users get utility links but not the environment information promised in the description.

Add a toolbar section that gathers and displays the promised page or environment information.

Comment thread dist-extension/toolbar.js
if (!items.length) return '';
return `<div class="menu-section"><div class="menu-section-title">${section.category}</div>${items.map((item) => `<button class="menu-item" type="button" data-id="${item.id || ''}" data-path="${item.path || ''}"><span class="menu-item-icon">${item.icon}</span><span class="menu-item-text">${item.label}<span class="menu-item-description">${item.description}</span></span>${item.shortcut ? `<span class="menu-item-shortcut">${item.shortcut}</span>` : ''}</button>`).join('')}</div>`;
}).join('<div class="menu-divider"></div>');
toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Comment thread dist-extension/toolbar.js
if (!items.length) return '';
return `<div class="menu-section"><div class="menu-section-title">${section.category}</div>${items.map((item) => `<button class="menu-item" type="button" data-id="${item.id || ''}" data-path="${item.path || ''}"><span class="menu-item-icon">${item.icon}</span><span class="menu-item-text">${item.label}<span class="menu-item-description">${item.description}</span></span>${item.shortcut ? `<span class="menu-item-shortcut">${item.shortcut}</span>` : ''}</button>`).join('')}</div>`;
}).join('<div class="menu-divider"></div>');
toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-innerhtml): User controlled data in a toolSections.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Comment thread extension/toolbar.js
if (!items.length) return '';
return `<div class="menu-section"><div class="menu-section-title">${section.category}</div>${items.map((item) => `<button class="menu-item" type="button" data-id="${item.id || ''}" data-path="${item.path || ''}"><span class="menu-item-icon">${item.icon}</span><span class="menu-item-text">${item.label}<span class="menu-item-description">${item.description}</span></span>${item.shortcut ? `<span class="menu-item-shortcut">${item.shortcut}</span>` : ''}</button>`).join('')}</div>`;
}).join('<div class="menu-divider"></div>');
toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Comment thread extension/toolbar.js
if (!items.length) return '';
return `<div class="menu-section"><div class="menu-section-title">${section.category}</div>${items.map((item) => `<button class="menu-item" type="button" data-id="${item.id || ''}" data-path="${item.path || ''}"><span class="menu-item-icon">${item.icon}</span><span class="menu-item-text">${item.label}<span class="menu-item-description">${item.description}</span></span>${item.shortcut ? `<span class="menu-item-shortcut">${item.shortcut}</span>` : ''}</button>`).join('')}</div>`;
}).join('<div class="menu-divider"></div>');
toolSections.innerHTML = html || '<div class="empty">No tools match your search.</div>';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-innerhtml): User controlled data in a toolSections.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

This branch had an error being deployed

1 failed and 2 active deployments
Preview – devtools — dd933cd1 Deployed Oct 8, 2026 by vercel[bot]
Preview – v0-project-review-tools — dd933cd1 Deployed Oct 8, 2026 by vercel[bot]
Preview – enclosed — dd933cd1 Deployed Oct 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants