chatGPTBox icon indicating copy to clipboard operation
chatGPTBox copied to clipboard

Add support for custom Selection Tools in right-click context menu

Open Copilot opened this issue 7 months ago • 23 comments

  • [x] Initial implementation plan
  • [x] Add custom Selection Tools support to right-click context menu
  • [x] Add menu refresh functionality to Selection Tools settings
  • [x] Fix parseInt usage with explicit radix and NaN checking
  • [x] Add error handling for Browser.runtime.sendMessage calls
  • [x] Remove unnecessary .catch(console.error) for REFRESH_MENU calls to match existing patterns
  • [x] Modernize code patterns: use forEach instead of traditional for loops and optional chaining for array access
  • [x] Reduce code duplication by extracting refreshContextMenu helper function
  • [x] Improve parseInt validation with NaN and bounds checking
  • [x] Add optional chaining for safer array access in background menus
  • [x] Test build and lint validation

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.


PR Type

Enhancement


Description

  • Add custom selection tools to right-click context menu

  • Implement real-time menu updates when settings change

  • Add proper error handling and validation

  • Filter context menu to show only active tools


Diagram Walkthrough

flowchart LR
  A["Settings UI"] -- "REFRESH_MENU" --> B["Background Script"]
  B -- "getUserConfig()" --> C["User Config"]
  C --> D["Context Menu Builder"]
  D --> E["Active Tools Only"]
  F["Right-click"] --> G["Content Script"]
  G -- "custom_ handler" --> H["Custom Tool Execution"]

File Walkthrough

Relevant files
Enhancement
menus.mjs
Enhanced context menu with custom tools support                   

src/background/menus.mjs

  • Add custom selection tool handling in onClickMenu
  • Filter context menu to show only active default tools
  • Add custom tools with custom_{index} IDs to menu
  • Enhance refreshMenu to fetch user config once
+31/-7   
index.jsx
Custom selection tool message processing                                 

src/content-script/index.jsx

  • Add custom tool message handling in runtime listener
  • Parse custom tool index with proper validation
  • Replace {{selection}} placeholder in custom prompts
  • Move getUserConfig call before conditional logic
+16/-1   
SelectionTools.jsx
Real-time menu refresh functionality                                         

src/popup/sections/SelectionTools.jsx

  • Add Browser import for runtime messaging
  • Send REFRESH_MENU messages on tool changes
  • Make event handlers async with error handling
  • Add .catch(console.error) to all sendMessage calls
+30/-9   

Summary by CodeRabbit

  • New Features

    • Added custom selection tools to the context menu with prompt support.
    • Context menu now reflects user-enabled default and custom tools.
    • Immediate menu updates after adding, editing, toggling, or deleting tools.
    • Selection-triggered chat uses appropriate prompts, including custom ones.
  • Refactor

    • Consolidated user configuration fetching to reduce redundant calls.
    • Menu rendering now respects the “hide context menu” setting and exits early.

Copilot avatar Aug 29 '25 19:08 Copilot

@copilot, address the feedback.

PeterDaveHello avatar Aug 29 '25 19:08 PeterDaveHello

@copilot, address the feedback.

I notice all the feedback comments are about reasoning model implementations (OpenAI API changes, model detection regex patterns, etc.) in files like src/services/apis/openai-api.mjs and src/utils/model-name-convert.mjs. However, my commits only modified selection tools functionality in src/background/menus.mjs, src/content-script/index.jsx, and src/popup/sections/SelectionTools.jsx.

Could you clarify which specific feedback relates to the selection tools changes I made? The reasoning model code referenced in the comments doesn't exist in the current codebase, so I believe there may be a mismatch between the feedback and my actual PR changes for issue #778.

Copilot avatar Aug 29 '25 19:08 Copilot

[!NOTE]

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Implements custom Selection Tools in the right‑click context menu: background builds menu from userConfig (active defaults and custom), click handling supports custom_ IDs and sends CREATE_CHAT. Content script resolves prompts for custom tools and renders FloatingToolbar. Popup updates config asynchronously and triggers REFRESH_MENU to rebuild the context menu.

Changes

Cohort / File(s) Summary
Background context menu integration
src/background/menus.mjs
- Adds handling for itemIds with prefix custom_ to dispatch CREATE_CHAT to the active tab
- Refactors refresh logic to load user config once and early-exit when hideContextMenu is true
- Builds menu from userConfig.activeSelectionTools and userConfig.customSelectionTools (active + named), generating IDs as ...custom_{index}
Content script prompt resolution
src/content-script/index.jsx
- Fetches userConfig at start of CREATE_CHAT handler
- Adds branch for custom_{index} to read customSelectionTools[index] when active and named, replacing {{selection}} in prompt
- Retains existing toolsConfig/menuConfig logic; removes redundant getUserConfig() before FloatingToolbar render
Popup config and menu refresh
src/popup/sections/SelectionTools.jsx
- Imports webextension-polyfill Browser and makes config mutations async with await updateConfig(...)
- After add/update/toggle/delete, sends { type: 'REFRESH_MENU' } to rebuild context menu; logs messaging errors
- Converts top-level and per-tool toggles and delete action to async flows

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Browser as Browser Context Menu
    participant BG as Background (menus.mjs)
    participant Tab as Active Tab
    participant CS as Content Script (index.jsx)
    participant UI as FloatingToolbar

    User->>Browser: Right-click selection
    Browser->>BG: onShown → build menu
    Note over BG: Load userConfig once<br/>- If hideContextMenu: return<br/>- Add active defaults<br/>- Add active custom tools (custom_{i})
    User->>Browser: Click menu item (e.g., custom_2)
    Browser->>BG: onClicked(itemId, tabId)
    alt itemId startsWith "custom_"
        BG->>Tab: sendMessage CREATE_CHAT { itemId, selection }
    else default handling
        BG->>Tab: sendMessage CREATE_CHAT { itemId, selection }
    end
    Tab->>CS: onMessage CREATE_CHAT
    CS->>CS: getUserConfig()
    alt custom_{i} valid & active with name
        CS->>CS: prompt = customSelectionTools[i].prompt.replace('{{selection}}', selection)
    else if toolsConfig/menuConfig match
        CS->>CS: prompt from config (language cropping if applicable)
    else
        CS->>CS: no-op/continue existing flow
    end
    CS->>UI: Render FloatingToolbar { prompt, selection, session config }
sequenceDiagram
    autonumber
    actor User
    participant Popup as Popup (SelectionTools.jsx)
    participant Ext as Browser.runtime
    participant BG as Background (menus.mjs)

    User->>Popup: Add/Edit/Toggle/Delete selection tool
    Popup->>Popup: await updateConfig({...})
    Popup->>Ext: sendMessage { type: REFRESH_MENU }
    Ext->>BG: REFRESH_MENU
    BG->>BG: refreshMenu() with latest userConfig
    Note over BG: Rebuild menu incl. active custom tools

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Expose custom Selection Tools in right‑click context menu (#778)
Honor enable/disable state for custom and default tools in context menu (#778)
Handle clicks on custom tools to run with current selection and tool prompt (#778)

Poem

I twitch my whiskers at a tidy menu tree,
Custom carrots—click!—now served contextually. 🥕
From popup fields to tabs that chat,
A prompt hops in where selection’s at.
Refresh!—the garden blooms anew,
Right‑click magic, fast and true.

[!TIP]

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • [ ] 📝 Generate Docstrings
🧪 Generate unit tests
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch copilot/fix-778

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

coderabbitai[bot] avatar Aug 29 '25 20:08 coderabbitai[bot]

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

778 - PR Code Verified

Compliant requirements:

  • Make custom Selection Tools available via the right-click context menu in addition to the floating toolbar.
  • Respect user preferences: only show enabled/active default and custom tools.
  • Ensure behavior is consistent between floating toolbar and context menu.
  • Provide reliable access on sites where the floating toolbar may not appear.
  • Allow adding, editing, deleting, and toggling custom tools and have the context menu reflect changes without restarting the extension.

Requires further human verification:

  • Manual testing across different sites where the floating toolbar fails, to verify context menu options appear and function.
  • UI/UX verification in the popup settings to ensure updates feel instantaneous and error states are clear.
  • Localization check for newly surfaced titles (custom tool names vs translated defaults).
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Null Safety

Accessing userConfig.customSelectionTools and userConfig.activeSelectionTools assumes arrays; add nullish coalescing or defaults to avoid runtime errors if undefined.

for (let i = 0; i < userConfig.customSelectionTools.length; i++) {
  const tool = userConfig.customSelectionTools[i]
  if (tool.active && tool.name) {
    Browser.contextMenus.create({
      id: menuId + 'custom_' + i,
      parentId: menuId,
      title: tool.name,
      contexts: ['selection'],
    })
  }
Template Handling

Custom prompt replacement only handles a single '{{selection}}' occurrence via String.replace; consider replacing all occurrences and handling missing or malformed templates more robustly.

// Handle custom selection tools from context menu
const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
if (
  !isNaN(customIndex) &&
  customIndex >= 0 &&
  userConfig.customSelectionTools &&
  customIndex < userConfig.customSelectionTools.length
) {
  const customTool = userConfig.customSelectionTools[customIndex]
  if (customTool.active && customTool.name) {
    prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
  }
}
Permission/Context Checks

Using Browser.tabs.sendMessage on context click assumes content script is injected; consider handling failures (e.g., no receiver) with try/catch or runtime.lastError for better resilience.

if (defaultConfig.selectionTools.includes(message.itemId)) {
  Browser.tabs.sendMessage(currentTab.id, {
    type: 'CREATE_CHAT',
    data: message,
  })
} else if (message.itemId.startsWith('custom_')) {
  // Handle custom selection tools
  Browser.tabs.sendMessage(currentTab.id, {
    type: 'CREATE_CHAT',
    data: message,
  })

qodo-code-review[bot] avatar Aug 29 '25 20:08 qodo-code-review[bot]

@coderabbitai review

PeterDaveHello avatar Aug 29 '25 20:08 PeterDaveHello

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot] avatar Aug 29 '25 20:08 coderabbitai[bot]

PR Code Suggestions ✨

Latest suggestions up to 7a86413

CategorySuggestion                                                                                                                                    Impact
Possible issue
Harden custom prompt handling

Prevent errors if itemId is not a string and ensure prompt is a string
containing the {{selection}} placeholder before replace. Add type checks and a
fallback to avoid undefined prompts.

src/content-script/index.jsx [291-299]

-} else if (data.itemId.startsWith('custom_')) {
+} else if (typeof data.itemId === 'string' && data.itemId.startsWith('custom_')) {
   // Handle custom selection tools from context menu
-  const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
-  if (!isNaN(customIndex) && customIndex >= 0) {
+  const customIndex = parseInt(data.itemId.slice('custom_'.length), 10)
+  if (Number.isInteger(customIndex) && customIndex >= 0) {
     const customTool = userConfig.customSelectionTools?.[customIndex]
-    if (customTool?.active && customTool?.name) {
-      prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
+    if (customTool?.active && customTool?.name && typeof customTool.prompt === 'string') {
+      const template = customTool.prompt
+      prompt = template.includes('{{selection}}')
+        ? template.replace('{{selection}}', data.selectionText ?? '')
+        : template
     }
   }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion enhances robustness by adding multiple checks (data.itemId type, customTool.prompt type, {{selection}} placeholder presence) and provides a safe fallback, preventing potential runtime errors and ensuring predictable behavior.

Medium
Validate custom tool prompt

Ensure custom tool prompts exist before creating menu items to avoid later
failures when generating prompts. Add a check that tool.prompt is a non-empty
string.

src/background/menus.mjs [88-97]

 userConfig.customSelectionTools?.forEach((tool, i) => {
-  if (tool?.active && tool?.name) {
+  if (tool?.active && tool?.name && typeof tool.prompt === 'string' && tool.prompt.trim().length > 0) {
     Browser.contextMenus.create({
       id: menuId + 'custom_' + i,
       parentId: menuId,
       title: tool.name,
       contexts: ['selection'],
     })
   }
 })
  • [ ] Apply / Chat <!-- /improve --apply_suggestion=1 -->
Suggestion importance[1-10]: 7

__

Why: This is a good suggestion to prevent creating context menu items for custom tools that lack a prompt, which would fail later. It improves robustness by ensuring all necessary data is present upfront.

Medium
Add defensive type check

Guard against message.itemId being undefined or not a string to prevent runtime
errors. Add a type-safe check before calling startsWith to avoid crashes from
unexpected messages.

src/background/menus.mjs [22-27]

-} else if (message.itemId.startsWith('custom_')) {
+} else if (typeof message.itemId === 'string' && message.itemId.startsWith('custom_')) {
   // Handle custom selection tools
   Browser.tabs.sendMessage(currentTab.id, {
     type: 'CREATE_CHAT',
     data: message,
   })
 }
  • [ ] Apply / Chat <!-- /improve --apply_suggestion=2 -->
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies a potential runtime error if message.itemId is not a string, but the risk is low as itemId is set by the extension itself, making this a minor improvement for robustness.

Low
  • [ ] More <!-- /improve --more_suggestions=true -->

Previous suggestions

Suggestions up to commit ee84477
CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate custom tool parsing

Add type checks and index validation to prevent crashes when itemId is malformed
or customSelectionTools is missing. Also fallback safely if prompt is absent and
ensure data.selectionText is a string before replacement.

src/content-script/index.jsx [291-297]

-} else if (data.itemId.startsWith('custom_')) {
+} else if (typeof data.itemId === 'string' && data.itemId.startsWith('custom_')) {
   // Handle custom selection tools from context menu
-  const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
-  const customTool = userConfig.customSelectionTools?.[customIndex]
-  if (customTool?.active && customTool?.name) {
-    prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
+  const customIndex = Number.parseInt(data.itemId.slice('custom_'.length), 10)
+  const tools = Array.isArray(userConfig.customSelectionTools) ? userConfig.customSelectionTools : []
+  if (Number.isInteger(customIndex) && customIndex >= 0 && customIndex < tools.length) {
+    const customTool = tools[customIndex]
+    if (customTool?.active && customTool?.name && typeof customTool.prompt === 'string') {
+      const selection = typeof data.selectionText === 'string' ? data.selectionText : ''
+      prompt = customTool.prompt.replace('{{selection}}', selection)
+    }
   }
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion adds comprehensive validation for parsing data.itemId and accessing customSelectionTools, making the code more robust against malformed data and preventing potential runtime errors.

Medium
Add defensive checks for item and tab

Validate that message.itemId is a string before calling startsWith to avoid
runtime crashes when other messages arrive. Also defensively ensure
currentTab?.id exists before sending the message to prevent unhandled errors on
closed or missing tabs.

src/background/menus.mjs [22-27]

-} else if (message.itemId.startsWith('custom_')) {
+} else if (typeof message.itemId === 'string' && message.itemId.startsWith('custom_')) {
   // Handle custom selection tools
-  Browser.tabs.sendMessage(currentTab.id, {
-    type: 'CREATE_CHAT',
-    data: message,
-  })
+  if (currentTab && currentTab.id) {
+    Browser.tabs.sendMessage(currentTab.id, {
+      type: 'CREATE_CHAT',
+      data: message,
+    })
+  } else {
+    console.warn('No active tab to send custom selection tool message')
+  }
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion adds useful defensive checks for message.itemId and currentTab.id, which can prevent potential runtime errors if the message format is unexpected or the tab is not available.

Low
Safeguard custom tools iteration

Guard against customSelectionTools being undefined or not an array to avoid
exceptions for first-time users or corrupted configs. Also ensure unique, string
IDs by explicitly converting the index and tool name to strings.

src/background/menus.mjs [88-97]

-userConfig.customSelectionTools.forEach((tool, i) => {
-  if (tool.active && tool.name) {
+const customTools = Array.isArray(userConfig.customSelectionTools) ? userConfig.customSelectionTools : []
+customTools.forEach((tool, i) => {
+  if (tool && tool.active && tool.name) {
     Browser.contextMenus.create({
-      id: menuId + 'custom_' + i,
+      id: String(menuId) + 'custom_' + String(i),
       parentId: menuId,
-      title: tool.name,
+      title: String(tool.name),
       contexts: ['selection'],
     })
   }
 })
Suggestion importance[1-10]: 5

__

Why: The suggestion adds a check to ensure userConfig.customSelectionTools is an array before iteration, which improves robustness against corrupted or missing configuration, although this scenario is unlikely.

Low
Suggestions up to commit 0ea8bef
CategorySuggestion                                                                                                                                    Impact
Possible issue
Harden custom tool parsing

Add type/exists checks to avoid exceptions when itemId is missing or not a
string, and ensure prompt is defined before calling replace. Fall back
gracefully if template missing.

src/content-script/index.jsx [291-310]

-} else if (data.itemId.startsWith('custom_')) {
+} else if (typeof data.itemId === 'string' && data.itemId.startsWith('custom_')) {
   // Handle custom selection tools from context menu
   const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
   if (
-    !isNaN(customIndex) &&
+    Number.isInteger(customIndex) &&
     customIndex >= 0 &&
-    userConfig.customSelectionTools &&
+    userConfig?.customSelectionTools &&
     customIndex < userConfig.customSelectionTools.length
   ) {
     const customTool = userConfig.customSelectionTools[customIndex]
-    if (customTool.active && customTool.name) {
-      prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
+    if (customTool?.active && customTool?.name && typeof customTool.prompt === 'string') {
+      prompt = customTool.prompt.includes('{{selection}}')
+        ? customTool.prompt.replace('{{selection}}', data.selectionText || '')
+        : customTool.prompt + ' ' + (data.selectionText || '')
     }
   }
 } else if (data.itemId in menuConfig) {
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies potential runtime errors and improves robustness by adding checks for data.itemId and customTool.prompt before they are used.

Medium
Possible issue
Guard string operation on itemId

Verify message.itemId is a string before calling startsWith to avoid runtime
errors when it's missing or non-string. Add a safe guard to ensure background
listener won't throw and break menu interaction.

src/background/menus.mjs [22-27]

-} else if (message.itemId.startsWith('custom_')) {
+} else if (typeof message.itemId === 'string' && message.itemId.startsWith('custom_')) {
   // Handle custom selection tools
   Browser.tabs.sendMessage(currentTab.id, {
     type: 'CREATE_CHAT',
     data: message,
   })
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that message.itemId could be non-string, and calling .startsWith() would cause a runtime error, so adding a type check improves code robustness.

Medium
Possible issue
Use safe array iteration

Avoid using for...in over arrays, which iterates enumerable keys and can include
unexpected properties. Switch to a standard indexed loop to ensure alignment
between selectionTools and selectionToolsDesc.

src/background/menus.mjs [74-85]

-for (const index in defaultConfig.selectionTools) {
-  const key = defaultConfig.selectionTools[index]
-  const desc = defaultConfig.selectionToolsDesc[index]
-  if (userConfig.activeSelectionTools.includes(key)) {
+for (let i = 0; i < defaultConfig.selectionTools.length; i++) {
+  const key = defaultConfig.selectionTools[i]
+  const desc = defaultConfig.selectionToolsDesc?.[i] ?? key
+  if (userConfig.activeSelectionTools?.includes(key)) {
     Browser.contextMenus.create({
       id: menuId + key,
       parentId: menuId,
       title: t(desc),
       contexts: ['selection'],
     })
   }
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that using for...in on an array is not best practice and can lead to bugs. The proposed change to a standard for loop improves code correctness, robustness, and aligns with JavaScript best practices.

Medium
Safeguard null user config

Guard against userConfig being null/undefined to prevent runtime errors that
would abort menu creation. Early-return safely if the config object is missing.

src/background/menus.mjs [46-47]

 const userConfig = await getUserConfig()
-if (userConfig.hideContextMenu) return
+if (!userConfig || userConfig.hideContextMenu) return
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential TypeError if getUserConfig returns a falsy value, and the proposed fix is a good defensive programming practice that improves the code's robustness.

Low
General
Handle message send failures

Handle potential rejections when sending runtime messages to avoid unhandled
promise rejections that can disrupt the popup. Wrap the call in a try/catch and
ignore benign "receiving end does not exist" errors.

src/popup/sections/SelectionTools.jsx [59-61]

-Browser.runtime.sendMessage({
-  type: 'REFRESH_MENU',
-})
+try {
+  await Browser.runtime.sendMessage({ type: 'REFRESH_MENU' })
+} catch (err) {
+  // Ignore if background is unavailable or no listener exists
+  if (process.env.NODE_ENV !== 'production') console.debug('REFRESH_MENU sendMessage error:', err)
+}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that Browser.runtime.sendMessage can throw an error, leading to an unhandled promise rejection. Wrapping it in a try/catch block is a valid improvement for error handling and application stability.

Medium
Suggestions up to commit fe4b856
CategorySuggestion                                                                                                                                    Impact
High-level
Align menu item IDs end-to-end

Background builds IDs with a menuId prefix (e.g., menuId + key, menuId +
'custom_' + i) while the content script matches raw keys and checks
startsWith('custom_'), so clicks won’t be recognized for default or custom
tools. Normalize IDs by stripping/adding the prefix consistently (or use raw
keys as IDs) in both the onClicked handler and content-script routing. Also add
a null-safe guard before iterating userConfig.customSelectionTools to prevent
crashes when the array is absent.

Examples:

src/background/menus.mjs [77-98]
      if (userConfig.activeSelectionTools.includes(key)) {
        Browser.contextMenus.create({
          id: menuId + key,
          parentId: menuId,
          title: t(desc),
          contexts: ['selection'],
        })
      }
    }


 ... (clipped 12 lines)
src/content-script/index.jsx [289-305]
      if (data.itemId in toolsConfig) {
        prompt = await toolsConfig[data.itemId].genPrompt(data.selectionText)
      } else if (data.itemId.startsWith('custom_')) {
        // Handle custom selection tools from context menu
        const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
        if (
          !isNaN(customIndex) &&
          customIndex >= 0 &&
          userConfig.customSelectionTools &&
          customIndex < userConfig.customSelectionTools.length

 ... (clipped 7 lines)

Solution Walkthrough:

Before:

// src/background/menus.mjs
function refreshMenu() {
  const menuId = 'chatgptbox-menu-'; // Example prefix
  // ...
  for (const index in defaultConfig.selectionTools) {
    const key = defaultConfig.selectionTools[index];
    Browser.contextMenus.create({ id: menuId + key, ... });
  }
  // ...
  for (let i = 0; i < userConfig.customSelectionTools.length; i++) { // No null check
    Browser.contextMenus.create({ id: menuId + 'custom_' + i, ... });
  }
}

// src/content-script/index.jsx
Browser.runtime.onMessage.addListener(async (message) => {
  const data = message.data;
  if (data.itemId in toolsConfig) { // Fails: 'chatgptbox-menu-summarize' is not in toolsConfig
    // ...
  } else if (data.itemId.startsWith('custom_')) { // Fails: ID has prefix
    // ...
  }
});

After:

// src/background/menus.mjs
function refreshMenu() {
  const menuId = 'chatgptbox-menu-'; // Example prefix
  // ...
  // ID creation remains the same
  // ...
  if (userConfig.customSelectionTools) { // Add null check
    for (let i = 0; i < userConfig.customSelectionTools.length; i++) {
      Browser.contextMenus.create({ id: menuId + 'custom_' + i, ... });
    }
  }
}

// src/content-script/index.jsx
Browser.runtime.onMessage.addListener(async (message) => {
  const data = message.data;
  const menuIdPrefix = 'chatgptbox-menu-'; // Define or get prefix
  const itemId = data.itemId.replace(menuIdPrefix, ''); // Strip prefix

  if (itemId in toolsConfig) { // Now works
    // ...
  } else if (itemId.startsWith('custom_')) { // Now works
    // ...
  }
});

Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical bug where menu item IDs created in menus.mjs with a menuId prefix are not handled correctly in index.jsx, which would make the entire feature non-functional.

High
Possible issue
Guard against undefined arrays

Guard against undefined or non-array config fields to prevent runtime errors on
fresh installs or corrupted configs. Default to empty arrays before iterating or
calling includes. This avoids the context menu failing to build.

src/background/menus.mjs [73-98]

 // Add default selection tools that are active
+const activeDefaults = Array.isArray(userConfig.activeSelectionTools) ? userConfig.activeSelectionTools : []
 for (const index in defaultConfig.selectionTools) {
   const key = defaultConfig.selectionTools[index]
   const desc = defaultConfig.selectionToolsDesc[index]
-  if (userConfig.activeSelectionTools.includes(key)) {
+  if (activeDefaults.includes(key)) {
     Browser.contextMenus.create({
       id: menuId + key,
       parentId: menuId,
       title: t(desc),
       contexts: ['selection'],
     })
   }
 }
 
 // Add custom selection tools that are active
-for (let i = 0; i < userConfig.customSelectionTools.length; i++) {
-  const tool = userConfig.customSelectionTools[i]
-  if (tool.active && tool.name) {
+const customTools = Array.isArray(userConfig.customSelectionTools) ? userConfig.customSelectionTools : []
+for (let i = 0; i < customTools.length; i++) {
+  const tool = customTools[i]
+  if (tool && tool.active && tool.name) {
     Browser.contextMenus.create({
       id: menuId + 'custom_' + i,
       parentId: menuId,
       title: tool.name,
       contexts: ['selection'],
     })
   }
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential runtime error if userConfig.activeSelectionTools or userConfig.customSelectionTools are not arrays. Adding these checks improves the code's robustness against corrupted or uninitialized configuration.

Low
Possible issue
Type-check itemId before prefix check

Guard .startsWith('custom_') with a string check to avoid crashes when itemId is
missing or not a string. This prevents background errors from malformed click
events.

src/background/menus.mjs [22-27]

-} else if (message.itemId.startsWith('custom_')) {
+} else if (typeof message.itemId === 'string' && message.itemId.startsWith('custom_')) {
   // Handle custom selection tools
   Browser.tabs.sendMessage(currentTab.id, {
     type: 'CREATE_CHAT',
     data: message,
   })
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly adds a type check for message.itemId before calling .startsWith(), which improves code robustness by preventing potential runtime errors.

Medium
Add null checks and safe replace

Avoid blindly calling replace on a potentially undefined prompt to prevent
runtime errors. Also, sanitize/escape data.selectionText insertions to avoid
breaking templates or injection issues. Add null checks and a safe replace
helper.

src/content-script/index.jsx [291-305]

 } else if (data.itemId.startsWith('custom_')) {
   // Handle custom selection tools from context menu
   const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
   if (
-    !isNaN(customIndex) &&
+    Number.isInteger(customIndex) &&
     customIndex >= 0 &&
-    userConfig.customSelectionTools &&
+    Array.isArray(userConfig.customSelectionTools) &&
     customIndex < userConfig.customSelectionTools.length
   ) {
     const customTool = userConfig.customSelectionTools[customIndex]
-    if (customTool.active && customTool.name) {
-      prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
+    if (customTool?.active && customTool?.name && typeof customTool.prompt === 'string') {
+      const selection = String(data.selectionText ?? '')
+      prompt = customTool.prompt.replace('{{selection}}', selection)
     }
   }
 } else if (data.itemId in menuConfig) {
Suggestion importance[1-10]: 6

__

Why: The suggestion improves code robustness by adding more specific type checks and handling potential null values, which prevents possible runtime errors.

Low
Possible issue
Fallback when menu position is missing

Validate menuX/menuY before using them; if they are undefined (e.g., message
triggered without a recent right-click), fall back to the centered position to
avoid NaN coordinates and rendering off-screen.

src/content-script/index.jsx [312-315]

-const position = data.useMenuPosition
+const hasMenuPos = data.useMenuPosition && Number.isFinite(menuX) && Number.isFinite(menuY)
+const position = hasMenuPos
   ? { x: menuX, y: menuY }
   : { x: window.innerWidth / 2 - 300, y: window.innerHeight / 2 - 200 }
 const container = createElementAtPosition(position.x, position.y)
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that menuX and menuY might be undefined and adds a check to fall back to a default position, preventing the component from rendering with NaN coordinates.

Medium
Guard against invalid menu click data

Ensure the clicked itemId exists before accessing it to prevent background
crashes. Add a guard for missing currentTab as well to avoid sending messages to
an undefined tab in edge cases (e.g., tab closed quickly).

src/background/menus.mjs [17-27]

+if (!message || typeof message.itemId !== 'string' || !currentTab || !currentTab.id) return
 if (defaultConfig.selectionTools.includes(message.itemId)) {
   Browser.tabs.sendMessage(currentTab.id, {
     type: 'CREATE_CHAT',
     data: message,
   })
 } else if (message.itemId.startsWith('custom_')) {
   // Handle custom selection tools
   Browser.tabs.sendMessage(currentTab.id, {
     type: 'CREATE_CHAT',
     data: message,
   })
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion adds a defensive check for message and currentTab before they are used, which can prevent potential runtime errors in edge cases, improving the robustness of the menu click handler.

Low
General
Robust placeholder validation for prompts

Normalize and strictly check the placeholder to avoid false negatives due to
whitespace or casing, and prevent saving templates that will break replacement.
Trim and enforce exact {{selection}} token presence.

src/popup/sections/SelectionTools.jsx [46-49]

-if (!editingTool.prompt.includes('{{selection}}')) {
+const promptTemplate = (editingTool.prompt ?? '').toString()
+if (!/\{\{\s*selection\s*\}\}/.test(promptTemplate)) {
   setErrorMessage(t('Prompt template should include {{selection}}'))
   return
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion improves the validation for the {{selection}} placeholder by using a regular expression to allow for variations in whitespace, making the check more flexible and robust.

Low
General
Harden language loading failure

If getPreferredLanguageKey() rejects, the menu rebuild will fail silently and
leave listeners in an inconsistent state. Wrap the language fetch in try/catch
and proceed with a safe default to avoid breaking the context menu.

src/background/menus.mjs [47-50]

 if (userConfig.hideContextMenu) return
 
-await getPreferredLanguageKey().then((lang) => {
+try {
+  const lang = await getPreferredLanguageKey()
   changeLanguage(lang)
+} catch (e) {
+  console.warn('Failed to get preferred language, falling back.', e)
+}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential unhandled promise rejection and proposes a robust try/catch block to prevent the menu setup from failing silently.

Medium
Skip when prompt is empty

Abort early when no valid prompt is produced to avoid creating an empty toolbar.
Add a guard after prompt resolution to return if it's falsy.

src/content-script/index.jsx [284-316]

 if (message.type === 'CREATE_CHAT') {
   const data = message.data
   let prompt = ''
   const userConfig = await getUserConfig()
 
   if (data.itemId in toolsConfig) {
     prompt = await toolsConfig[data.itemId].genPrompt(data.selectionText)
   } else if (data.itemId.startsWith('custom_')) {
     // Handle custom selection tools from context menu
     const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
     if (
       !isNaN(customIndex) &&
       customIndex >= 0 &&
       userConfig.customSelectionTools &&
       customIndex < userConfig.customSelectionTools.length
     ) {
       const customTool = userConfig.customSelectionTools[customIndex]
       if (customTool.active && customTool.name) {
         prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
       }
     }
   } else if (data.itemId in menuConfig) {
     const menuItem = menuConfig[data.itemId]
     if (!menuItem.genPrompt) return
     else prompt = await menuItem.genPrompt()
     if (prompt) prompt = await cropText(`Reply in ${await getPreferredLanguage()}.\n` + prompt)
   }
 
+  if (!prompt) return
+
   const position = data.useMenuPosition
     ? { x: menuX, y: menuY }
     : { x: window.innerWidth / 2 - 300, y: window.innerHeight / 2 - 200 }
   const container = createElementAtPosition(position.x, position.y)
   container.className = 'chatgptbox-toolbar-container-not-queryable'
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that an empty prompt can be generated and adds a necessary check to prevent creating an empty, non-functional toolbar.

Low

qodo-code-review[bot] avatar Aug 29 '25 20:08 qodo-code-review[bot]

@codex review

PeterDaveHello avatar Aug 29 '25 20:08 PeterDaveHello

Codex Review: Didn't find any major issues. Chef's kiss.

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

@copilot, address all the feedback.

PeterDaveHello avatar Aug 29 '25 20:08 PeterDaveHello

@copilot, address all the feedback.

I've addressed the feedback by removing unnecessary .catch(console.error) calls from the REFRESH_MENU sendMessage operations to match the existing pattern in the codebase (e.g., AdvancedPart.jsx uses the same message type without error handling). The parseInt logic with explicit radix and NaN checking remains robust. Changes committed in 0ea8bef.

Copilot avatar Aug 29 '25 20:08 Copilot

Persistent suggestions updated to latest commit 0ea8bef

qodo-code-review[bot] avatar Aug 29 '25 20:08 qodo-code-review[bot]

@codex review

PeterDaveHello avatar Aug 29 '25 20:08 PeterDaveHello

Codex Review: Didn't find any major issues. What shall we delve into next?

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

@copilot, address all the feedback.

PeterDaveHello avatar Aug 29 '25 20:08 PeterDaveHello

@copilot, address all the feedback.

I've reviewed the code and both linting (npm run lint) and building (npm run build) pass successfully. The comment threads reference specific lines but don't contain specific feedback to address. Could you provide more details about what specific issues need to be resolved? The current implementation follows the established patterns in the codebase and includes proper error handling.

Copilot avatar Aug 29 '25 20:08 Copilot

@copilot, address all the feedback.

All feedback has been addressed. I've modernized the code patterns to use forEach instead of traditional for loops and simplified the validation logic with optional chaining. The code now follows the established patterns in the codebase while maintaining the same functionality. All changes pass lint and build validation. Changes made in commit ee84477.

Copilot avatar Aug 30 '25 09:08 Copilot

Persistent suggestions updated to latest commit ee84477

qodo-code-review[bot] avatar Aug 30 '25 09:08 qodo-code-review[bot]

@copilot, address ALL the feedback in the comments and reviews from CodeRabbit, Qoro Merge, and Copilot.

I've addressed the feedback by:

  1. Reduced code duplication: Extracted refreshContextMenu() helper function to eliminate repeated Browser.runtime.sendMessage calls
  2. Improved robustness: Added NaN validation and bounds checking for parseInt results
  3. Enhanced null safety: Added optional chaining for customSelectionTools array access in background menus

All changes maintain existing functionality while following better coding practices. Build and lint validation passes. (7a86413)

Copilot avatar Aug 30 '25 09:08 Copilot

@codex review

PeterDaveHello avatar Aug 30 '25 09:08 PeterDaveHello

Persistent suggestions updated to latest commit 7a86413

qodo-code-review[bot] avatar Aug 30 '25 09:08 qodo-code-review[bot]

Codex Review: Didn't find any major issues. Bravo.

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

@copilot, address all the feedback.

I've reviewed the code and all linting and building passes successfully. The comment threads reference specific lines but don't contain detailed feedback to address. Could you provide more specifics about what issues need to be resolved? The current implementation follows established patterns in the codebase and includes proper error handling and validation.

Copilot avatar Aug 30 '25 09:08 Copilot