Skip to content

Conversation

@Simon-He95
Copy link
Collaborator

@Simon-He95 Simon-He95 commented Dec 13, 2025

Summary by CodeRabbit

  • New Features

    • Added tooltip overlays that display helpful text when hovering over app controls.
    • Expanded UI with new actions: scroll left/right, history, minimize, maximize, and restore.
  • Internationalization

    • Added translations for new UI actions across all supported languages.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

This PR introduces a tooltip overlay system for the Electron app. It adds a new renderer build entry for the tooltip overlay HTML, implements window lifecycle management for tooltip overlays in the main process, integrates hover-triggered tooltip interactions in the AppBar UI, creates a tooltip overlay renderer module with IPC event handlers, and adds localized UI strings across 11 language files for tooltip-related actions.

Changes

Cohort / File(s) Summary
Build Configuration
electron.vite.config.ts
Adds new renderer build input for tooltip overlay pointing to src/renderer/shell/tooltip-overlay/index.html.
Main Process Window Management
src/main/presenter/windowPresenter/index.ts
Introduces tooltip overlay window management with state maps (tooltipOverlayWindows, pendingTooltipPayload), IPC handlers for show/hide, lazy-initialization of overlay windows with bounds synchronization, and lifecycle integration (pre-creation on shell load, cleanup on blur/close). Includes helper methods: getOrCreateTooltipOverlay(), syncTooltipOverlayBounds(), clearTooltipOverlay(), destroyTooltipOverlay(). Note: Contains duplicated tooltip overlay method definitions.
Renderer UI Components
src/renderer/shell/components/AppBar.vue, src/renderer/shell/components/app-bar/AppBarTabItem.vue
AppBar.vue adds reactive tooltip system with hover handlers across buttons (scroll, new tab, history, settings, minimize, maximize, close), debounce timer, position tracking via IPC (shell-tooltip:show/hide), and lifecycle cleanup. AppBarTabItem.vue adds window-no-drag-region CSS class to disable drag behavior.
Tooltip Overlay Renderer
src/renderer/shell/tooltip-overlay/index.html, src/renderer/shell/tooltip-overlay/main.ts
index.html provides minimal HTML scaffold with root #app element and module import. main.ts implements fixed tooltip DOM with show/hide behavior driven by IPC events (shell-tooltip-overlay:show, shell-tooltip-overlay:hide, shell-tooltip-overlay:clear), coordinates positioning, and high z-index layering.
Internationalization
src/renderer/src/i18n/{da-DK,en-US,fa-IR,fr-FR,he-IL,ja-JP,ko-KR,pt-BR,ru-RU,zh-CN,zh-HK,zh-TW}/common.json
Adds six new localization keys across 11 language files: scrollLeft, scrollRight, history, minimize, maximize, restore. Updates saving entries with trailing commas to accommodate new keys.

Sequence Diagram

sequenceDiagram
    participant User
    participant AppBar as AppBar.vue<br/>(Renderer)
    participant IPC as IPC Bridge
    participant WinPres as WindowPresenter<br/>(Main)
    participant Overlay as TooltipOverlay<br/>Window
    
    User->>AppBar: Hover over button
    AppBar->>AppBar: Start tooltipTimer<br/>(debounce)
    AppBar->>IPC: shell-tooltip:show<br/>(x, y, text)
    IPC->>WinPres: Receive show event
    WinPres->>WinPres: getOrCreateTooltipOverlay()
    WinPres->>Overlay: Create/show window<br/>if not exists
    WinPres->>IPC: shell-tooltip-overlay:show<br/>(payload)
    IPC->>Overlay: Receive show event
    Overlay->>Overlay: Update text, position,<br/>set visible
    Overlay-->>User: Tooltip displayed
    
    User->>AppBar: Mouse leave
    AppBar->>IPC: shell-tooltip:hide
    IPC->>WinPres: Receive hide event
    WinPres->>Overlay: Send hide signal
    Overlay->>Overlay: Hide tooltip
    Overlay-->>User: Tooltip hidden
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • WindowPresenter duplicated methods: The main process file contains noted duplication of tooltip overlay utility methods that requires careful verification and refactoring assessment.
  • IPC event flow consistency: Verify that all IPC channels (shell-tooltip:show, shell-tooltip:hide, shell-tooltip-overlay:*) are properly wired across main and renderer processes with correct payload types.
  • Tooltip window lifecycle: Ensure proper cleanup on window blur, close, and edge cases to prevent memory leaks with overlay window references in tooltipOverlayWindows map.
  • Hover interaction timing: Review debounce logic and timer management in AppBar.vue to prevent tooltip flickering or memory leaks from uncanceled timers.

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • zerob13

Poem

🐰 Hovering whiskers, a tooltip appears,
IPC bridges connect without fears,
Windows dance in the overlay night,
Translations bloom in polyglot light,
A tooltip waltz, perfectly right! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(shell): overlay tooltips for AppBar' clearly summarizes the main change: implementing tooltip overlays for the AppBar component in the shell module.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (7)
src/renderer/src/i18n/pt-BR/common.json (1)

82-87: Apply hierarchical naming to new translation keys per coding guidelines.

The new keys (scrollLeft, scrollRight, history, minimize, maximize, restore) use flat naming, but the coding guidelines require dot-separated hierarchical structure (e.g., common.button.submit). These keys represent distinct feature areas (window controls, history, appbar actions) and should use nested naming to reflect their context.

Suggested structure:

{
  ...
  "saving": "Salvando",
  "appbar": {
    "scrollLeft": "Rolar para a esquerda",
    "scrollRight": "Rolar para a direita"
  },
  "common": {
    "history": "Histórico"
  },
  "window": {
    "minimize": "Minimizar",
    "maximize": "Maximizar",
    "restore": "Restaurar"
  }
}

Alternatively, if keeping a flat structure, use dot notation:

{
  "appbar.scrollLeft": "Rolar para a esquerda",
  "appbar.scrollRight": "Rolar para a direita",
  "common.history": "Histórico",
  "window.minimize": "Minimizar",
  "window.maximize": "Maximizar",
  "window.restore": "Restaurar"
}

Ensure all corresponding locale files are updated consistently with the same hierarchical structure.

src/renderer/src/i18n/zh-TW/common.json (1)

82-87: Consider adopting hierarchical key structure across translation files.

The new UI keys (scrollLeft, scrollRight, history, minimize, maximize, restore) follow the existing flat camelCase pattern in this file. However, the coding guideline specifies using dot-separated hierarchical structure (e.g., common.button.submit). These keys could benefit from hierarchical naming such as:

  • appbar.scrollLeft, appbar.scrollRight
  • window.minimize, window.maximize, window.restore
  • common.history

This would improve consistency with the stated naming convention, though it would require refactoring across all 11 language files mentioned in the PR. Given the broader scope, this can be deferred to a separate consistency pass.

The translations themselves are accurate and idiomatic for Traditional Chinese (Taiwan).

src/renderer/src/i18n/zh-CN/common.json (1)

82-87: Consider adopting dot-separated hierarchical naming for new keys.

The new keys (scrollLeft, scrollRight, history, minimize, maximize, restore) use camelCase at the root level. Per the i18n coding guidelines, translation keys should follow a dot-separated hierarchical structure (e.g., appbar.scrollLeft, window.minimize) for better organization and maintainability.

While the existing file has a mixed pattern, new keys for this feature should establish a clearer hierarchy. This would improve discoverability and reduce key collisions as the translation set grows.

Consider refactoring to use hierarchical naming:

-  "scrollLeft": "向左滚动",
-  "scrollRight": "向右滚动",
-  "history": "历史",
-  "minimize": "最小化",
-  "maximize": "最大化",
-  "restore": "还原"
+  "appbar.scrollLeft": "向左滚动",
+  "appbar.scrollRight": "向右滚动",
+  "appbar.history": "历史",
+  "window.minimize": "最小化",
+  "window.maximize": "最大化",
+  "window.restore": "还原"

Or, group under a single top-level category if the keys belong together:

-  "scrollLeft": "向左滚动",
-  "scrollRight": "向右滚动",
-  "history": "历史",
-  "minimize": "最小化",
-  "maximize": "最大化",
-  "restore": "还原"
+  "tooltip": {
+    "scrollLeft": "向左滚动",
+    "scrollRight": "向右滚动",
+    "history": "历史",
+    "minimize": "最小化",
+    "maximize": "最大化",
+    "restore": "还原"
+  }

If these keys are added with the same naming pattern across all 11 language files, consider whether the code consuming these translations has been updated to match the new key paths.

src/renderer/src/i18n/fa-IR/common.json (1)

82-87: Consider grouping tooltip keys under a parent category.

The coding guidelines specify using "dot-separated hierarchical structure" for organization. These tooltip-related keys could be grouped under a parent key (e.g., tooltip: { scrollLeft, scrollRight, ... }) for better hierarchy and consistency with the pattern used elsewhere in the file (e.g., the error object). This would align the key structure with the stated naming conventions while maintaining readability.

src/main/presenter/windowPresenter/index.ts (1)

956-961: Pre-creating the overlay is a nice UX optimization; consider gating if memory becomes a concern.

src/renderer/shell/tooltip-overlay/main.ts (1)

3-7: Consider adding type safety and error boundaries.

While the implementation is functional, consider:

  1. The type definition could be moved to a shared types file for reuse across the codebase
  2. Adding basic error handling in the show function for invalid coordinates or text

Example enhancement:

+// Validate payload before showing
 const show = (payload: TooltipOverlayShowPayload) => {
+  if (!payload.text || typeof payload.x !== 'number' || typeof payload.y !== 'number') {
+    console.warn('Invalid tooltip payload:', payload)
+    return
+  }
+
   textNode.textContent = payload.text
   
   tooltip.style.left = `${payload.x}px`
   tooltip.style.top = `${payload.y}px`
   tooltip.style.transform = 'translate(-50%, 0)'
   
   tooltip.classList.remove('hidden')
 }

Also applies to: 36-44

src/renderer/shell/components/AppBar.vue (1)

253-258: Consider throttling position updates during scroll.

The updateTooltipPosition is called on every scroll event via requestAnimationFrame. For smoother performance with many tabs, consider throttling position updates.

Example using a throttle helper:

import { useThrottleFn } from '@vueuse/core'

// In component setup
const throttledUpdatePosition = useThrottleFn(updateTooltipPosition, 16) // ~60fps

const onTabContainerWrapperScroll = () => {
  requestAnimationFrame(() => {
    tabContainerWrapperScrollLeft.value = tabContainerWrapper.value?.scrollLeft ?? 0
    throttledUpdatePosition()
  })
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a28fd27 and 860e1d1.

📒 Files selected for processing (18)
  • electron.vite.config.ts (1 hunks)
  • src/main/presenter/windowPresenter/index.ts (6 hunks)
  • src/renderer/shell/components/AppBar.vue (9 hunks)
  • src/renderer/shell/components/app-bar/AppBarTabItem.vue (2 hunks)
  • src/renderer/shell/tooltip-overlay/index.html (1 hunks)
  • src/renderer/shell/tooltip-overlay/main.ts (1 hunks)
  • src/renderer/src/i18n/da-DK/common.json (1 hunks)
  • src/renderer/src/i18n/en-US/common.json (1 hunks)
  • src/renderer/src/i18n/fa-IR/common.json (1 hunks)
  • src/renderer/src/i18n/fr-FR/common.json (1 hunks)
  • src/renderer/src/i18n/he-IL/common.json (1 hunks)
  • src/renderer/src/i18n/ja-JP/common.json (1 hunks)
  • src/renderer/src/i18n/ko-KR/common.json (1 hunks)
  • src/renderer/src/i18n/pt-BR/common.json (1 hunks)
  • src/renderer/src/i18n/ru-RU/common.json (1 hunks)
  • src/renderer/src/i18n/zh-CN/common.json (1 hunks)
  • src/renderer/src/i18n/zh-HK/common.json (1 hunks)
  • src/renderer/src/i18n/zh-TW/common.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for logs and comments (Chinese text exists in legacy code, but new code should use English)

Files:

  • electron.vite.config.ts
  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enable and maintain strict TypeScript type checking for all files

**/*.{ts,tsx}: Always use try-catch to handle possible errors in TypeScript code
Provide meaningful error messages when catching errors
Log detailed error logs including error details, context, and stack traces
Distinguish and handle different error types (UserError, NetworkError, SystemError, BusinessError) with appropriate handlers in TypeScript
Use structured logging with logger.error(), logger.warn(), logger.info(), logger.debug() methods from logging utilities
Do not suppress errors (avoid empty catch blocks or silently ignoring errors)
Provide user-friendly error messages for user-facing errors in TypeScript components
Implement error retry mechanisms for transient failures in TypeScript
Avoid logging sensitive information (passwords, tokens, PII) in logs

Files:

  • electron.vite.config.ts
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Do not include AI co-authoring information (e.g., 'Co-Authored-By: Claude') in git commits

Files:

  • electron.vite.config.ts
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
**/*.{js,ts,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

Write logs and comments in English

Files:

  • electron.vite.config.ts
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
src/renderer/src/i18n/**/*.json

📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)

src/renderer/src/i18n/**/*.json: Translation key naming convention: use dot-separated hierarchical structure with lowercase letters and descriptive names (e.g., 'common.button.submit')
Maintain consistent key-value structure across all language translation files (zh-CN, en-US, ko-KR, ru-RU, zh-HK, fr-FR, fa-IR)

Files:

  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/he-IL/common.json
  • src/renderer/src/i18n/da-DK/common.json
  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/pt-BR/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/ru-RU/common.json
src/**/*

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

New features should be developed in the src directory

Files:

  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/he-IL/common.json
  • src/renderer/src/i18n/da-DK/common.json
  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/renderer/shell/tooltip-overlay/index.html
  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/pt-BR/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/main/presenter/windowPresenter/index.ts
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/ru-RU/common.json
src/renderer/**

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

Use lowercase with dashes for directories (e.g., components/auth-wizard)

Files:

  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/he-IL/common.json
  • src/renderer/src/i18n/da-DK/common.json
  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/renderer/shell/tooltip-overlay/index.html
  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/pt-BR/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/ru-RU/common.json
**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.vue: Use Vue 3 Composition API for all components instead of Options API
Use Tailwind CSS with scoped styles for component styling

Files:

  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
src/renderer/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/renderer/**/*.vue: All user-facing strings must use i18n keys via vue-i18n for internationalization
Ensure proper error handling and loading states in all UI components
Implement responsive design using Tailwind CSS utilities for all UI components

src/renderer/**/*.vue: Use composition API and declarative programming patterns; avoid options API
Structure files: exported component, composables, helpers, static content, types
Use PascalCase for component names (e.g., AuthWizard.vue)
Use Vue 3 with TypeScript, leveraging defineComponent and PropType
Use template syntax for declarative rendering
Use Shadcn Vue, Radix Vue, and Tailwind for components and styling
Implement responsive design with Tailwind CSS; use a mobile-first approach
Use Suspense for asynchronous components
Use <script setup> syntax for concise component definitions
Prefer 'lucide:' icon family as the primary choice for Iconify icons
Import Icon component from '@iconify/vue' and use with lucide icons following pattern '{collection}:{icon-name}'

Files:

  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
src/renderer/**/*.{vue,js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Renderer process code should be placed in src/renderer (Vue 3 application)

Files:

  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
src/renderer/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

src/renderer/**/*.{ts,tsx,vue}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Avoid enums; use const objects instead
Use arrow functions for methods and computed properties
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements

Vue 3 app code in src/renderer/src should be organized into components/, stores/, views/, i18n/, lib/ directories with shell UI in src/renderer/shell/

Files:

  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
src/renderer/**/*.{ts,vue}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

src/renderer/**/*.{ts,vue}: Use useFetch and useAsyncData for data fetching
Leverage ref, reactive, and computed for reactive state management
Use provide/inject for dependency injection when appropriate
Use Iconify/Vue for icon implementation

Files:

  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
src/**/*.{ts,tsx,vue,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with single quotes, no semicolons, and 100 character width

Files:

  • src/renderer/shell/components/app-bar/AppBarTabItem.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
src/renderer/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use the usePresenter.ts composable for renderer-to-main IPC communication to call presenter methods directly

Files:

  • src/renderer/shell/tooltip-overlay/main.ts
{src/main/presenter/**/*.ts,src/renderer/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/electron-best-practices.mdc)

Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs

Files:

  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

Use TypeScript for all code; prefer types over interfaces

Files:

  • src/renderer/shell/tooltip-overlay/main.ts
src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use OxLint for linting JavaScript and TypeScript files

Files:

  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,tsx}: Use camelCase for variable and function names in TypeScript files
Use PascalCase for type and class names in TypeScript
Use SCREAMING_SNAKE_CASE for constant names

Files:

  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use EventBus for inter-process communication events

Files:

  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
src/main/presenter/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Organize core business logic into dedicated Presenter classes, with one presenter per functional domain

Files:

  • src/main/presenter/windowPresenter/index.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use EventBus from src/main/eventbus.ts for main-to-renderer communication, broadcasting events via mainWindow.webContents.send()

src/main/**/*.ts: Use EventBus pattern for inter-process communication within the main process to decouple modules
Use Electron's built-in APIs for file system and native dialogs instead of Node.js or custom implementations

src/main/**/*.ts: Electron main process code belongs in src/main/ with presenters in presenter/ (Window/Tab/Thread/Mcp/Config/LLMProvider) and eventbus.ts for app events
Use the Presenter pattern in the main process for UI coordination

Files:

  • src/main/presenter/windowPresenter/index.ts
src/main/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Main process code for Electron should be placed in src/main

Files:

  • src/main/presenter/windowPresenter/index.ts
🧠 Learnings (28)
📓 Common learnings
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:26:24.867Z
Learning: Applies to {src/main/presenter/**/*.ts,src/renderer/**/*.ts} : Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs
📚 Learning: 2025-11-25T05:26:24.867Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:26:24.867Z
Learning: Applies to {src/main/presenter/**/*.ts,src/renderer/**/*.ts} : Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs

Applied to files:

  • electron.vite.config.ts
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
📚 Learning: 2025-10-12T00:58:06.513Z
Learnt from: zerob13
Repo: ThinkInAIXYZ/deepchat PR: 977
File: package.json:137-137
Timestamp: 2025-10-12T00:58:06.513Z
Learning: In this Electron project, renderer process dependencies (used only in src/renderer/) should be placed in devDependencies because they are bundled by electron-vite during the build process. Only main process dependencies (used in src/main/) need to be in the dependencies section.

Applied to files:

  • electron.vite.config.ts
📚 Learning: 2025-11-25T05:26:24.867Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:26:24.867Z
Learning: Applies to src/main/**/*.ts : Use Electron's built-in APIs for file system and native dialogs instead of Node.js or custom implementations

Applied to files:

  • electron.vite.config.ts
  • src/renderer/shell/tooltip-overlay/main.ts
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Use TypeScript with Vue 3 Composition API for the renderer application

Applied to files:

  • electron.vite.config.ts
  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/renderer/**/*.{ts,tsx,vue} : Vue 3 app code in `src/renderer/src` should be organized into `components/`, `stores/`, `views/`, `i18n/`, `lib/` directories with shell UI in `src/renderer/shell/`

Applied to files:

  • electron.vite.config.ts
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.{ts,tsx,vue} : Write concise, technical TypeScript code with accurate examples

Applied to files:

  • electron.vite.config.ts
  • src/renderer/shell/tooltip-overlay/main.ts
📚 Learning: 2025-11-25T05:27:39.200Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/provider-guidelines.mdc:0-0
Timestamp: 2025-11-25T05:27:39.200Z
Learning: Applies to **/*Provider**/index.ts : Do not introduce renderer dependencies inside Provider implementations

Applied to files:

  • electron.vite.config.ts
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.vue : Structure files: exported component, composables, helpers, static content, types

Applied to files:

  • electron.vite.config.ts
📚 Learning: 2025-11-25T05:26:43.510Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-11-25T05:26:43.510Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Maintain consistent key-value structure across all language translation files (zh-CN, en-US, ko-KR, ru-RU, zh-HK, fr-FR, fa-IR)

Applied to files:

  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/he-IL/common.json
  • src/renderer/src/i18n/da-DK/common.json
  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/pt-BR/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/ru-RU/common.json
📚 Learning: 2025-11-25T05:26:43.510Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-11-25T05:26:43.510Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming convention: use dot-separated hierarchical structure with lowercase letters and descriptive names (e.g., 'common.button.submit')

Applied to files:

  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/he-IL/common.json
  • src/renderer/src/i18n/da-DK/common.json
  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/pt-BR/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/ru-RU/common.json
📚 Learning: 2025-11-25T05:26:43.510Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-11-25T05:26:43.510Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Avoid hardcoding user-facing text and ensure all user-visible text uses the i18n translation system

Applied to files:

  • src/renderer/src/i18n/zh-CN/common.json
📚 Learning: 2025-11-25T05:27:45.545Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:27:45.545Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx,js,jsx} : Use the Composition API for better code organization and reusability in Vue.js applications

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.{ts,vue} : Use Iconify/Vue for icon implementation

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/composables/*.ts : Use VueUse for common composables and utility functions

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.{ts,vue} : Leverage ref, reactive, and computed for reactive state management

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.vue : Use <script setup> syntax for concise component definitions

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:26:11.312Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:26:11.312Z
Learning: Applies to src/renderer/**/*.ts : Use the `usePresenter.ts` composable for renderer-to-main IPC communication to call presenter methods directly

Applied to files:

  • src/renderer/shell/components/AppBar.vue
  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.vue : Use composition API and declarative programming patterns; avoid options API

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.vue : Use Vue 3 with TypeScript, leveraging defineComponent and PropType

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:27:45.545Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:27:45.545Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx,js,jsx} : Leverage Vue's built-in reactivity system for efficient data handling

Applied to files:

  • src/renderer/shell/components/AppBar.vue
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/main/**/*.ts : Electron main process code belongs in `src/main/` with presenters in `presenter/` (Window/Tab/Thread/Mcp/Config/LLMProvider) and `eventbus.ts` for app events

Applied to files:

  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
📚 Learning: 2025-11-25T05:26:24.867Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:26:24.867Z
Learning: Applies to src/shared/**/*.d.ts : Define type definitions in shared/*.d.ts files for objects exposed by the main process to the renderer process

Applied to files:

  • src/renderer/shell/tooltip-overlay/main.ts
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/main/**/*.ts : Use the Presenter pattern in the main process for UI coordination

Applied to files:

  • src/renderer/shell/tooltip-overlay/main.ts
  • src/main/presenter/windowPresenter/index.ts
📚 Learning: 2025-11-25T05:27:26.656Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T05:27:26.656Z
Learning: Applies to src/shared/**/*.{js,ts} : Shared type definitions and utilities between main and renderer processes should be placed in `src/shared`

Applied to files:

  • src/renderer/shell/tooltip-overlay/main.ts
📚 Learning: 2025-11-25T05:28:04.454Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-11-25T05:28:04.454Z
Learning: Applies to src/renderer/**/*.{ts,tsx} : Use TypeScript for all code; prefer types over interfaces

Applied to files:

  • src/renderer/shell/tooltip-overlay/main.ts
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/preload/**/*.ts : Secure IPC bridge should be implemented in `src/preload/` with contextIsolation enabled

Applied to files:

  • src/renderer/shell/tooltip-overlay/main.ts
📚 Learning: 2025-11-25T05:26:11.312Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:26:11.312Z
Learning: Applies to src/main/presenter/mcpPresenter/**/*.ts : Register new MCP tools in `mcpPresenter/index.ts` after implementing them in `inMemoryServers/`

Applied to files:

  • src/main/presenter/windowPresenter/index.ts
🧬 Code graph analysis (2)
src/renderer/shell/tooltip-overlay/main.ts (1)
test/mocks/electron.ts (1)
  • ipcRenderer (18-23)
src/main/presenter/windowPresenter/index.ts (2)
test/mocks/electron.ts (2)
  • BrowserWindow (36-51)
  • ipcMain (12-16)
test/mocks/electron-toolkit-utils.ts (1)
  • is (1-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (19)
src/renderer/src/i18n/fa-IR/common.json (2)

81-81: Trailing comma correctly added.

The comma after "saving" properly accommodates the new entries that follow.


82-87: Tooltip keys are consistent across all language files.

All six tooltip keys (scrollLeft, scrollRight, history, minimize, maximize, restore) are present in all language translation files (en-US, zh-CN, ko-KR, ru-RU, zh-HK, fr-FR, and fa-IR), maintaining the required consistent key-value structure.

src/renderer/src/i18n/he-IL/common.json (1)

82-87: Consider organizing new tooltip keys under a hierarchical parent key.

The six new keys (scrollLeft, scrollRight, history, minimize, maximize, restore) are added as flat root-level keys. Per coding guidelines, translation keys should use dot-separated hierarchical structure with lowercase descriptive names (e.g., common.button.submit). While these keys follow the existing flat-key pattern in the file, they could be better organized under a parent key since they're all tooltip-related UI actions and window controls.

Consider restructuring as:

"tooltip": {
  "scrollLeft": "גלול שמאלה",
  "scrollRight": "גלול ימינה",
  "history": "היסטוריה",
  "minimize": "מזער",
  "maximize": "הגדל",
  "restore": "שחזר"
}

However, this should match the structure used in other language files to maintain consistency.
[request_verification, suggest_recommended_refactor]

Verify that:

  1. These keys are added with the same structure (flat vs. nested) across all 11 language files mentioned in the AI summary
  2. The hierarchical naming convention is consistently applied or if flat keys are acceptable for general UI actions in this project
#!/bin/bash
# Verify consistency of new tooltip keys across language files
# Check if the keys exist in other i18n files and their structure

find src/renderer/src/i18n -name "common.json" -type f | while read file; do
  echo "=== Checking $file ==="
  grep -E '"(scrollLeft|scrollRight|history|minimize|maximize|restore)"' "$file" || echo "Missing keys in $file"
done
src/renderer/src/i18n/da-DK/common.json (1)

81-87: Locale parity looks consistent with the new tooltip/AppBar actions.

Added scrollLeft, scrollRight, history, minimize, maximize, restore aligns with the PR’s new UI actions and matches the en-US key set.

src/renderer/src/i18n/zh-HK/common.json (1)

81-87: LGTM: keys added + kept consistent with other locales.

src/renderer/src/i18n/ja-JP/common.json (1)

81-87: LGTM: keys added + consistent with the new AppBar tooltip actions.

src/main/presenter/windowPresenter/index.ts (4)

43-45: Good direction: per-window overlay state + pending payload tracking is straightforward.
Based on learnings/coding guidelines, keeping this scoped by parentWindow.id is the right granularity for IPC-driven overlays.


767-778: Good lifecycle hook: clear tooltip on parent blur avoids “stuck tooltip”.


911-929: Good cleanup: destroy overlay on parent window close.
This should prevent orphaned overlay windows and payload state.


71-107: [rewritten comment]
[classification tag]

src/renderer/src/i18n/en-US/common.json (1)

81-87: i18n keys follow the established flat camelCase convention and are correctly distributed across locales.

The added keys (scrollLeft, scrollRight, history, minimize, maximize, restore) match the existing naming pattern in the file and are present across all locale common.json files. The documented guideline for dot-separated lowercase naming does not reflect the actual implementation pattern used throughout the codebase; the established convention is flat camelCase, which these keys correctly follow. No action required.

src/renderer/shell/components/app-bar/AppBarTabItem.vue (1)

5-5: LGTM! Correct implementation of non-draggable region for Electron.

The addition of the window-no-drag-region class and corresponding scoped style correctly disables window dragging for tab items, enabling stable hover interactions for the new tooltip overlay system.

Also applies to: 60-65

electron.vite.config.ts (1)

105-105: LGTM! Correctly adds tooltip overlay entry point.

The new shellTooltipOverlay entry follows the existing pattern and correctly points to the tooltip overlay HTML file.

src/renderer/shell/tooltip-overlay/index.html (1)

1-12: LGTM! Clean HTML entry point for tooltip overlay.

The minimal HTML structure is appropriate for an overlay window, with transparent background and module script loading correctly configured.

src/renderer/src/i18n/ru-RU/common.json (1)

82-87: LGTM! Translation keys correctly added.

The new translation keys for tooltip actions follow the naming convention and are consistent with the tooltip overlay feature. Ensure these same keys are present across all language files.

src/renderer/src/i18n/fr-FR/common.json (1)

82-87: LGTM! French translations correctly added.

The translation keys match the structure added to other language files and properly support the tooltip overlay feature.

src/renderer/shell/components/AppBar.vue (2)

187-216: LGTM! Well-implemented tooltip lifecycle management.

The tooltip system correctly implements:

  • Debounced hover (200ms) to prevent flickering
  • Proper cleanup on unmount (timer and tooltip hiding)
  • Position updates on resize and scroll
  • Type-safe state management

The IPC channel naming issue is addressed in the tooltip-overlay/main.ts review.

Also applies to: 576-582


218-251: Well-structured hover event handlers.

The hover logic correctly:

  • Tracks the currently hovered target to prevent race conditions
  • Clears timers on leave to cancel pending tooltips
  • Uses unique keys for each hoverable element (including tab IDs)
src/renderer/shell/tooltip-overlay/main.ts (1)

46-56: The IPC channel architecture is correct and working as designed. The main process (windowPresenter) intentionally bridges renderer channels (shell-tooltip:show, shell-tooltip:hide) to overlay window channels (shell-tooltip-overlay:show, shell-tooltip-overlay:hide), as shown in the handler implementation. This is proper inter-process communication design—not a channel mismatch issue.

Likely an incorrect or invalid review comment.

Comment on lines +1033 to +1142
private getOrCreateTooltipOverlay(parentWindow: BrowserWindow): BrowserWindow | null {
if (parentWindow.isDestroyed()) return null

const existing = this.tooltipOverlayWindows.get(parentWindow.id)
if (existing && !existing.isDestroyed()) {
this.syncTooltipOverlayBounds(parentWindow, existing)
if (!existing.isVisible()) {
existing.showInactive()
}
return existing
}

const bounds = parentWindow.getContentBounds()

const overlay = new BrowserWindow({
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
parent: parentWindow,
show: false,
frame: false,
transparent: true,
backgroundColor: '#00000000',
resizable: false,
movable: false,
minimizable: false,
maximizable: false,
closable: false,
hasShadow: false,
focusable: false,
skipTaskbar: true,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, '../preload/index.mjs'),
sandbox: false,
devTools: is.dev
}
})

overlay.setIgnoreMouseEvents(true, { forward: true })

const sync = () => {
const current = this.tooltipOverlayWindows.get(parentWindow.id)
if (!current || current.isDestroyed() || parentWindow.isDestroyed()) return
this.syncTooltipOverlayBounds(parentWindow, current)
}

parentWindow.on('move', sync)
parentWindow.on('resize', sync)
parentWindow.on('show', () => {
if (!overlay.isDestroyed()) overlay.showInactive()
})
parentWindow.on('hide', () => {
if (!overlay.isDestroyed()) overlay.hide()
})
parentWindow.on('minimize', () => {
if (!overlay.isDestroyed()) overlay.hide()
})
parentWindow.on('restore', () => {
if (!overlay.isDestroyed()) overlay.showInactive()
})

overlay.on('closed', () => {
this.tooltipOverlayWindows.delete(parentWindow.id)
this.pendingTooltipPayload.delete(parentWindow.id)
})

if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
overlay.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/shell/tooltip-overlay/index.html')
} else {
overlay.loadFile(join(__dirname, '../renderer/shell/tooltip-overlay/index.html'))
}

overlay.webContents.once('did-finish-load', () => {
if (overlay.isDestroyed()) return
overlay.showInactive()
overlay.webContents.send('shell-tooltip-overlay:clear')

const pending = this.pendingTooltipPayload.get(parentWindow.id)
if (pending) {
overlay.webContents.send('shell-tooltip-overlay:show', pending)
}
})

this.tooltipOverlayWindows.set(parentWindow.id, overlay)
return overlay
}

private syncTooltipOverlayBounds(parentWindow: BrowserWindow, overlay: BrowserWindow): void {
if (parentWindow.isDestroyed() || overlay.isDestroyed()) return
const bounds = parentWindow.getContentBounds()
overlay.setBounds(bounds)
}

private clearTooltipOverlay(windowId: number): void {
const overlay = this.tooltipOverlayWindows.get(windowId)
if (!overlay || overlay.isDestroyed()) return
this.pendingTooltipPayload.delete(windowId)
overlay.webContents.send('shell-tooltip-overlay:hide')
}

private destroyTooltipOverlay(windowId: number): void {
const overlay = this.tooltipOverlayWindows.get(windowId)
if (overlay && !overlay.isDestroyed()) {
overlay.destroy()
}
this.tooltipOverlayWindows.delete(windowId)
this.pendingTooltipPayload.delete(windowId)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the exact code at the specified lines
cat -n src/main/presenter/windowPresenter/index.ts | sed -n '1033,1142p'

Repository: ThinkInAIXYZ/deepchat

Length of output: 4394


🏁 Script executed:

# Check the full context of the windowPresenter file to understand patterns
wc -l src/main/presenter/windowPresenter/index.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 113


🏁 Script executed:

# Look for how tooltips are managed and destroyed
rg -A 5 -B 5 "tooltipOverlayWindows" src/main/presenter/windowPresenter/index.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 3152


🏁 Script executed:

# Check if there are other overlay window management patterns in the codebase
rg -A 10 "overlay\.on\('closed'" src/

Repository: ThinkInAIXYZ/deepchat

Length of output: 967


🏁 Script executed:

# Verify error handling patterns for webContents.send() in the codebase
rg "webContents\.send" src/main/presenter/windowPresenter/index.ts | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 1360


🏁 Script executed:

# Check if try-catch is used for webContents.send() elsewhere
rg -B 3 -A 3 "webContents\.send" src/main/ | grep -A 3 "try\|catch" | head -30

Repository: ThinkInAIXYZ/deepchat

Length of output: 1912


🏁 Script executed:

# Check how overlay is called to understand if recreation scenario is realistic
rg "getOrCreateTooltipOverlay|destroyTooltipOverlay" src/main/presenter/windowPresenter/index.ts -B 2 -A 2

Repository: ThinkInAIXYZ/deepchat

Length of output: 1015


🏁 Script executed:

# Check the parent window lifecycle to understand if parent stays alive while overlay is destroyed
rg "shell-tooltip" src/main/presenter/windowPresenter/index.ts | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 544


Remove parentWindow event listeners when overlay is destroyed to prevent listener accumulation, check parent visibility before showing overlay, and add error handling for webContents.send() calls.

getOrCreateTooltipOverlay() attaches listeners to parentWindow every time a new overlay is created. When an overlay is destroyed (due to crash, reload, or manual destruction) while the parent window remains alive, the listeners are never removed. If the overlay is recreated for the same parent, multiple listener instances accumulate, causing memory leaks and duplicate event handling.

Additionally, did-finish-load unconditionally calls overlay.showInactive() without checking if the parent window is hidden or minimized, potentially showing the overlay when it shouldn't be visible. Finally, webContents.send() calls lack error handling, inconsistent with the error handling pattern used elsewhere in this file for similar operations.

   const sync = () => {
     const current = this.tooltipOverlayWindows.get(parentWindow.id)
     if (!current || current.isDestroyed() || parentWindow.isDestroyed()) return
     this.syncTooltipOverlayBounds(parentWindow, current)
   }

+  const onShow = () => {
+    if (!overlay.isDestroyed()) overlay.showInactive()
+  }
+  const onHide = () => {
+    if (!overlay.isDestroyed()) overlay.hide()
+  }
+  const onMinimize = () => {
+    if (!overlay.isDestroyed()) overlay.hide()
+  }
+  const onRestore = () => {
+    if (!overlay.isDestroyed()) overlay.showInactive()
+  }
+
   parentWindow.on('move', sync)
   parentWindow.on('resize', sync)
-  parentWindow.on('show', () => {
-    if (!overlay.isDestroyed()) overlay.showInactive()
-  })
-  parentWindow.on('hide', () => {
-    if (!overlay.isDestroyed()) overlay.hide()
-  })
-  parentWindow.on('minimize', () => {
-    if (!overlay.isDestroyed()) overlay.hide()
-  })
-  parentWindow.on('restore', () => {
-    if (!overlay.isDestroyed()) overlay.showInactive()
-  })
+  parentWindow.on('show', onShow)
+  parentWindow.on('hide', onHide)
+  parentWindow.on('minimize', onMinimize)
+  parentWindow.on('restore', onRestore)

   overlay.on('closed', () => {
+    if (!parentWindow.isDestroyed()) {
+      parentWindow.removeListener('move', sync)
+      parentWindow.removeListener('resize', sync)
+      parentWindow.removeListener('show', onShow)
+      parentWindow.removeListener('hide', onHide)
+      parentWindow.removeListener('minimize', onMinimize)
+      parentWindow.removeListener('restore', onRestore)
+    }
     this.tooltipOverlayWindows.delete(parentWindow.id)
     this.pendingTooltipPayload.delete(parentWindow.id)
   })

   overlay.webContents.once('did-finish-load', () => {
     if (overlay.isDestroyed()) return
+    if (parentWindow.isDestroyed() || parentWindow.isMinimized() || !parentWindow.isVisible()) return
     overlay.showInactive()
-    overlay.webContents.send('shell-tooltip-overlay:clear')
+    try {
+      overlay.webContents.send('shell-tooltip-overlay:clear')
+    } catch (error) {
+      console.error('Failed to clear tooltip overlay:', error)
+    }

     const pending = this.pendingTooltipPayload.get(parentWindow.id)
     if (pending) {
-      overlay.webContents.send('shell-tooltip-overlay:show', pending)
+      try {
+        overlay.webContents.send('shell-tooltip-overlay:show', pending)
+      } catch (error) {
+        console.error('Failed to show pending tooltip payload:', error)
+      }
     }
   })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private getOrCreateTooltipOverlay(parentWindow: BrowserWindow): BrowserWindow | null {
if (parentWindow.isDestroyed()) return null
const existing = this.tooltipOverlayWindows.get(parentWindow.id)
if (existing && !existing.isDestroyed()) {
this.syncTooltipOverlayBounds(parentWindow, existing)
if (!existing.isVisible()) {
existing.showInactive()
}
return existing
}
const bounds = parentWindow.getContentBounds()
const overlay = new BrowserWindow({
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
parent: parentWindow,
show: false,
frame: false,
transparent: true,
backgroundColor: '#00000000',
resizable: false,
movable: false,
minimizable: false,
maximizable: false,
closable: false,
hasShadow: false,
focusable: false,
skipTaskbar: true,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, '../preload/index.mjs'),
sandbox: false,
devTools: is.dev
}
})
overlay.setIgnoreMouseEvents(true, { forward: true })
const sync = () => {
const current = this.tooltipOverlayWindows.get(parentWindow.id)
if (!current || current.isDestroyed() || parentWindow.isDestroyed()) return
this.syncTooltipOverlayBounds(parentWindow, current)
}
parentWindow.on('move', sync)
parentWindow.on('resize', sync)
parentWindow.on('show', () => {
if (!overlay.isDestroyed()) overlay.showInactive()
})
parentWindow.on('hide', () => {
if (!overlay.isDestroyed()) overlay.hide()
})
parentWindow.on('minimize', () => {
if (!overlay.isDestroyed()) overlay.hide()
})
parentWindow.on('restore', () => {
if (!overlay.isDestroyed()) overlay.showInactive()
})
overlay.on('closed', () => {
this.tooltipOverlayWindows.delete(parentWindow.id)
this.pendingTooltipPayload.delete(parentWindow.id)
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
overlay.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/shell/tooltip-overlay/index.html')
} else {
overlay.loadFile(join(__dirname, '../renderer/shell/tooltip-overlay/index.html'))
}
overlay.webContents.once('did-finish-load', () => {
if (overlay.isDestroyed()) return
overlay.showInactive()
overlay.webContents.send('shell-tooltip-overlay:clear')
const pending = this.pendingTooltipPayload.get(parentWindow.id)
if (pending) {
overlay.webContents.send('shell-tooltip-overlay:show', pending)
}
})
this.tooltipOverlayWindows.set(parentWindow.id, overlay)
return overlay
}
private syncTooltipOverlayBounds(parentWindow: BrowserWindow, overlay: BrowserWindow): void {
if (parentWindow.isDestroyed() || overlay.isDestroyed()) return
const bounds = parentWindow.getContentBounds()
overlay.setBounds(bounds)
}
private clearTooltipOverlay(windowId: number): void {
const overlay = this.tooltipOverlayWindows.get(windowId)
if (!overlay || overlay.isDestroyed()) return
this.pendingTooltipPayload.delete(windowId)
overlay.webContents.send('shell-tooltip-overlay:hide')
}
private destroyTooltipOverlay(windowId: number): void {
const overlay = this.tooltipOverlayWindows.get(windowId)
if (overlay && !overlay.isDestroyed()) {
overlay.destroy()
}
this.tooltipOverlayWindows.delete(windowId)
this.pendingTooltipPayload.delete(windowId)
}
private getOrCreateTooltipOverlay(parentWindow: BrowserWindow): BrowserWindow | null {
if (parentWindow.isDestroyed()) return null
const existing = this.tooltipOverlayWindows.get(parentWindow.id)
if (existing && !existing.isDestroyed()) {
this.syncTooltipOverlayBounds(parentWindow, existing)
if (!existing.isVisible()) {
existing.showInactive()
}
return existing
}
const bounds = parentWindow.getContentBounds()
const overlay = new BrowserWindow({
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
parent: parentWindow,
show: false,
frame: false,
transparent: true,
backgroundColor: '#00000000',
resizable: false,
movable: false,
minimizable: false,
maximizable: false,
closable: false,
hasShadow: false,
focusable: false,
skipTaskbar: true,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, '../preload/index.mjs'),
sandbox: false,
devTools: is.dev
}
})
overlay.setIgnoreMouseEvents(true, { forward: true })
const sync = () => {
const current = this.tooltipOverlayWindows.get(parentWindow.id)
if (!current || current.isDestroyed() || parentWindow.isDestroyed()) return
this.syncTooltipOverlayBounds(parentWindow, current)
}
const onShow = () => {
if (!overlay.isDestroyed()) overlay.showInactive()
}
const onHide = () => {
if (!overlay.isDestroyed()) overlay.hide()
}
const onMinimize = () => {
if (!overlay.isDestroyed()) overlay.hide()
}
const onRestore = () => {
if (!overlay.isDestroyed()) overlay.showInactive()
}
parentWindow.on('move', sync)
parentWindow.on('resize', sync)
parentWindow.on('show', onShow)
parentWindow.on('hide', onHide)
parentWindow.on('minimize', onMinimize)
parentWindow.on('restore', onRestore)
overlay.on('closed', () => {
if (!parentWindow.isDestroyed()) {
parentWindow.removeListener('move', sync)
parentWindow.removeListener('resize', sync)
parentWindow.removeListener('show', onShow)
parentWindow.removeListener('hide', onHide)
parentWindow.removeListener('minimize', onMinimize)
parentWindow.removeListener('restore', onRestore)
}
this.tooltipOverlayWindows.delete(parentWindow.id)
this.pendingTooltipPayload.delete(parentWindow.id)
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
overlay.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/shell/tooltip-overlay/index.html')
} else {
overlay.loadFile(join(__dirname, '../renderer/shell/tooltip-overlay/index.html'))
}
overlay.webContents.once('did-finish-load', () => {
if (overlay.isDestroyed()) return
if (parentWindow.isDestroyed() || parentWindow.isMinimized() || !parentWindow.isVisible()) return
overlay.showInactive()
try {
overlay.webContents.send('shell-tooltip-overlay:clear')
} catch (error) {
console.error('Failed to clear tooltip overlay:', error)
}
const pending = this.pendingTooltipPayload.get(parentWindow.id)
if (pending) {
try {
overlay.webContents.send('shell-tooltip-overlay:show', pending)
} catch (error) {
console.error('Failed to show pending tooltip payload:', error)
}
}
})
this.tooltipOverlayWindows.set(parentWindow.id, overlay)
return overlay
}
private syncTooltipOverlayBounds(parentWindow: BrowserWindow, overlay: BrowserWindow): void {
if (parentWindow.isDestroyed() || overlay.isDestroyed()) return
const bounds = parentWindow.getContentBounds()
overlay.setBounds(bounds)
}
private clearTooltipOverlay(windowId: number): void {
const overlay = this.tooltipOverlayWindows.get(windowId)
if (!overlay || overlay.isDestroyed()) return
this.pendingTooltipPayload.delete(windowId)
overlay.webContents.send('shell-tooltip-overlay:hide')
}
private destroyTooltipOverlay(windowId: number): void {
const overlay = this.tooltipOverlayWindows.get(windowId)
if (overlay && !overlay.isDestroyed()) {
overlay.destroy()
}
this.tooltipOverlayWindows.delete(windowId)
this.pendingTooltipPayload.delete(windowId)
}

@zerob13
Copy link
Collaborator

zerob13 commented Dec 15, 2025

@codex review

@Simon-He95
Copy link
Collaborator Author

录屏2025-12-15 09 59 29

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ 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
  • 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 address that feedback".

@zerob13
Copy link
Collaborator

zerob13 commented Dec 15, 2025

LGTM

@zerob13 zerob13 merged commit df6bad6 into ThinkInAIXYZ:dev Dec 15, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants