Skip to content

refactor(window): drop shell tab layer#1332

Merged
zerob13 merged 1 commit intodevfrom
refactor/no-more-multi-tabs
Mar 7, 2026
Merged

refactor(window): drop shell tab layer#1332
zerob13 merged 1 commit intodevfrom
refactor/no-more-multi-tabs

Conversation

@zerob13
Copy link
Collaborator

@zerob13 zerob13 commented Mar 6, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Multi-window architecture transition: replaced tab-based navigation with window-centric management for improved workflow organization.
    • Added support for new AI models: GPT-5.4, GPT-5.3-chat-latest, Grok-4.2-fast variants with enhanced capabilities.
  • Removed Features

    • Tab navigation shortcuts (Create/Close Tab, Switch Tab) replaced with window management controls.
    • Tooltip overlay feature removed as part of UI simplification.
  • UI/UX Updates

    • Updated shortcuts and settings to reflect window-based navigation.
    • Adjusted browser interface terminology from tab-centric to window-centric across all UI elements.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

This PR implements a comprehensive multi-window cleanup that transitions the application architecture from single-window, multi-tab to multi-window, single-page. Key changes include: refactoring window and presenter APIs to be window-centric, removing tab-based shortcuts and UI, updating event systems from tab to window terminology, migrating session bindings to webContents-based routing, renaming shell renderer to browser, removing tooltip overlay, and updating build configuration and localization throughout.

Changes

Cohort / File(s) Summary
Documentation & Planning
docs/specs/multi-window-cleanup/*
Added comprehensive specification documents including plan, detailed spec, and task checklist for the multi-window cleanup initiative.
Build Configuration
electron.vite.config.ts, tsconfig.app.json, tsconfig.app.tsgo.json, vitest.config.ts, vitest.config.renderer.ts
Updated build aliases and include paths from @shell/@shell to @browser/@browser; removed shell tooltip overlay entries from renderer inputs; replaced Tailwind glob source paths.
Event System & EventBus
src/main/events.ts, src/renderer/src/events.ts, src/main/eventbus.ts
Migrated event constants and routing from tab-centric (TAB_CREATED, TAB_CLOSED, etc.) to window-centric (WINDOW_CREATED, WINDOW_CLOSED, etc.); replaced tab-based event bus routing with webContents and DEFAULT_WINDOW targets; removed tabPresenter routing logic.
Window Presenter & Management
src/main/presenter/windowPresenter/index.ts, src/main/presenter/windowPresenter/FloatingChatWindow.ts, src/main/presenter/tabPresenter.ts, src/main/utils/index.ts
Refactored window lifecycle from shell-window to appWindow/browserWindow creation APIs; added sendToDefaultWindow and sendToWebContents methods; removed tooltip overlay management; updated window creation for moved tabs to preserve window type.
Browser Presenter (Core Refactor)
src/main/presenter/browser/YoBrowserPresenter.ts, src/main/presenter/browser/YoBrowserToolHandler.ts, src/main/presenter/browser/YoBrowserToolDefinitions.ts, src/main/presenter/browser/BrowserTab.ts, src/main/presenter/browser/BrowserContextBuilder.ts
Substantially rewrote YoBrowserPresenter from tab-centric to window-centric state management; renamed BrowserTab to BrowserPage with pageId field; introduced BrowserWindowState and BrowserWindowInfo types; replaced all tab-based navigation/lifecycle methods with window equivalents; updated tool definitions from tab_* to window_* actions.
Session & Conversation Management
src/main/presenter/sessionPresenter/index.ts, src/main/presenter/sessionPresenter/managers/conversationManager.ts, src/main/presenter/sessionPresenter/types.ts
Migrated session binding from tabId to webContentsId-based model; replaced activeConversationIds with activeConversationBindings; updated all session creation, binding, and lookup methods to use webContentsId; added window-aware session forking (openConversationInNewWindow).
Agent Presenter & Streaming
src/main/presenter/agentPresenter/index.ts, src/main/presenter/agentPresenter/utility/utilityHandler.ts, src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts, src/main/presenter/agentPresenter/streaming/types.ts, src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts, src/main/presenter/agentPresenter/streaming/llmEventHandler.ts, src/main/presenter/agentPresenter/loop/toolCallHandler.ts
Renamed tabId parameters to webContentsId across message sending, translation, AI features, and streaming delta updates; updated internal state and method signatures throughout the agent presenter pipeline.
Shortcut System
src/main/presenter/shortcutPresenter.ts, src/main/presenter/configPresenter/shortcutKeySettings.ts
Removed tab-related shortcuts (CREATE_NEW_TAB, CLOSE_CURRENT_TAB, SWITCH_TO_NEXT_TAB, etc.); added CloseWindow and DELETE_CONVERSATION shortcuts; updated shortcut handler routing to use windowPresenter and webContents instead of tab-based messaging.
MCP Servers & In-Memory Tools
src/main/presenter/mcpPresenter/index.ts, src/main/presenter/mcpPresenter/inMemoryServers/conversationSearchServer.ts, src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts
Removed create_new_tab tool from conversation-search server; simplified meeting server tool to legacy placeholder with removed in-process orchestration; updated sampling request routing to DEFAULT_WINDOW.
Deeplink & Dialog Routing
src/main/presenter/deeplinkPresenter/index.ts, src/main/presenter/dialogPresenter/index.ts
Renamed helper methods from shell to app window terminology; changed DEFAULT_TAB routing to DEFAULT_WINDOW; updated window readiness checks and MCP config storage logic for app window context.
IPC & Presenter Interfaces
src/main/presenter/index.ts, src/main/presenter/floatingButtonPresenter/index.ts, src/main/presenter/lifecyclePresenter/hooks/after-start/windowCreationHook.ts, src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts
Updated IPCCallContext to use windowId/webContentsId instead of tabId; changed initial window creation from createShellWindow to createAppWindow with route-based initialization; updated floating button window creation logic.
Type Definitions
src/shared/types/browser.ts, src/shared/types/presenters/legacy.presenters.d.ts, src/shared/types/presenters/agent.presenter.d.ts, src/shared/types/presenters/session.presenter.d.ts, src/shared/types/presenters/thread.presenter.d.ts
Renamed BrowserTabStatus to BrowserPageStatus; replaced BrowserTabInfo with BrowserPageInfo and BrowserWindowInfo; updated event definitions from tab-* to window-*; added webContents-based method signatures across all presenter interfaces; deprecated tab-based methods.
Renderer Browser Components (Refactor)
src/renderer/browser/App.vue, src/renderer/browser/components/AppBar.vue, src/renderer/browser/components/BrowserToolbar.vue, src/renderer/browser/index.html, src/renderer/browser/stores/window.ts
Added new AppBar component for browser window chrome; replaced tab-store dependency with new browserWindowStore; updated toolbar to use window-based navigation; added window state management store with IPC integration.
Removed Shell Components & Tab UI
src/renderer/shell/components/AppBar.vue, src/renderer/shell/components/app-bar/AppBarTabItem.vue, src/renderer/shell/stores/tab.ts, src/renderer/shell/tooltip-overlay/index.html, src/renderer/shell/tooltip-overlay/main.ts
Deleted entire AppBar component with tab management and drag-drop UI; removed AppBarTabItem tab renderer; deleted tab store with tab lifecycle management; removed tooltip overlay HTML and main module.
Renderer UI & Store Updates
src/renderer/src/components/WindowSideBar.vue, src/renderer/src/components/chat-input/SkillsIndicator.vue, src/renderer/src/components/mock/MockWelcomePage.vue, src/renderer/src/components/workspace/WorkspaceBrowserTabs.vue, src/renderer/src/composables/usePageCapture.ts, src/renderer/src/composables/usePresenter.ts, src/renderer/src/pages/WelcomePage.vue, src/renderer/src/stores/ui/pageRouter.ts, src/renderer/src/stores/yoBrowser.ts
Replaced tab-centric state with window-centric (tabs → windows, activeTabId → activeWindowId); updated settings window opening from tab-based to window-based API; refactored YoBrowser store to use window lifecycle events; simplified page capture target resolution to webContents/window precedence.
Localization Updates (All Languages)
src/renderer/src/i18n/*/common.json, src/renderer/src/i18n/*/settings.json, src/types/i18n.d.ts
Removed newTab translation key from all 11 language files; removed tab navigation shortcut keys (closeTab, nextTab, previousTab, specificTab, lastTab) and added closeWindow shortcut in all languages; updated i18n type definitions with new UI groups and removed tab-related entries.
Provider Configuration
resources/model-db/providers.json
Added new AI provider entries (gpt-5.4, gpt-5.3-chat-latest, gpt-5.4-pro, grok-4.2-fast variants) with associated model capabilities, context limits, and cost metadata.
Test Updates
test/main/eventbus/eventbus.test.ts, test/renderer/components/WindowSideBar.test.ts
Updated EventBus tests to verify DEFAULT_WINDOW routing and webContents-based broadcasting; replaced sendToDefaultTab assertions with sendToDefaultWindow; updated WindowSideBar test mocks to include new yoBrowserStore and openOrFocusSettingsWindow API.
Settings UI Components
src/renderer/settings/components/ShortcutSettings.vue
Simplified shortcut button visibility logic by removing NumberTabs fallback; removed tab-related shortcut entries from mapping; updated isNumberTabs field removal from computed shortcut objects.

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly Related PRs

  • refactor: full agent loop mode #1208: Modifies YoBrowserPresenter and related window/browser presenter APIs, directly intersecting with the core browser presenter refactor in this PR.
  • UI/refactor #977: Updates WindowPresenter implementation and window lifecycle event wiring, sharing substantial overlap with the window management layer restructuring.
  • feat: tab shortcutkey can be disabled #1223: Modifies shortcut system and keyboard bindings for tab navigation, directly related to tab-to-window shortcut migration and removal in this PR.

Poem

🐰 Hops with glee through multi-window dreams,
Tabs are gone—now windows reign supreme!
Shell becomes browser, fresh and bright,
Sessions dance on webContents light,
Shortcuts sing of windows, not of tabs,
A cleaner hop through all these paths! 🪟✨

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/no-more-multi-tabs

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: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/renderer/src/composables/usePageCapture.ts (1)

272-290: ⚠️ Potential issue | 🟠 Major

The window fallback is skipped when the first capture attempt throws.

The try/catch wraps the whole for ... of, so a rejected captureTabArea() for webContentsId breaks out before windowId is ever tried. Catch per target, or normalize presenter failures to null, so the advertised webContentsId -> windowId fallback actually runs.

🐛 Suggested change
-        try {
-          let segmentData: string | null = null
-          for (const targetId of captureTargets) {
-            segmentData = await tabPresenter.captureTabArea(targetId, captureRect)
-            if (segmentData) {
-              break
-            }
-          }
-
-          if (segmentData) {
-            imageDataList.push(segmentData)
-          } else {
-            console.error(`[CAPTURE_DEBUG] Iteration ${iteration}: 截图失败,未返回数据`)
-            break
-          }
-        } catch (captureError) {
-          console.error(`[CAPTURE_DEBUG] Iteration ${iteration}: 截图出错:`, captureError)
-          break
-        }
+        let segmentData: string | null = null
+        for (const targetId of captureTargets) {
+          try {
+            segmentData = await tabPresenter.captureTabArea(targetId, captureRect)
+            if (segmentData) {
+              break
+            }
+          } catch (captureError) {
+            console.error(
+              `[CAPTURE_DEBUG] Iteration ${iteration}: capture failed for target ${targetId}`,
+              captureError
+            )
+          }
+        }
+
+        if (segmentData) {
+          imageDataList.push(segmentData)
+        } else {
+          console.error(`[CAPTURE_DEBUG] Iteration ${iteration}: capture failed for all targets`)
+          break
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/composables/usePageCapture.ts` around lines 272 - 290, The
try/catch currently wraps the entire loop so a thrown rejection from
tabPresenter.captureTabArea aborts the whole fallback sequence; change the logic
so each target is protected individually: inside the for (const targetId of
captureTargets) loop, await tabPresenter.captureTabArea(targetId, captureRect)
inside its own try/catch (or ensure captureTabArea failures are normalized to
null) so on error you set segmentData = null (or continue) and allow the loop to
try the next targetId; keep the outer behavior that pushes segmentData to
imageDataList when non-null and logs/ breaks only when all targets return null.
src/main/presenter/tabPresenter.ts (1)

928-948: ⚠️ Potential issue | 🟠 Major

Choose the new window kind from the tab content, not the source window.

createTab() already allows non-local tabs in chat windows via allowNonLocal. In that case this branch creates another chat window, and attachTab() immediately rejects the moved external tab, so the move always rolls back.

🐛 Suggested fix
-    const sourceWindowType = this.getWindowType(originalWindowId)
+    const isLocalTab = tabInfo.url?.startsWith('local://')
     const newWindowOptions: Record<string, any> = {
       forMovedTab: true,
-      windowType: sourceWindowType
+      windowType: isLocalTab ? 'chat' : 'browser'
     }
@@
     const newWindowId =
-      sourceWindowType === 'browser'
-        ? await this.windowPresenter.createBrowserWindow({
-            x: newWindowOptions.x,
-            y: newWindowOptions.y
-          })
-        : await this.windowPresenter.createAppWindow({
+      isLocalTab
+        ? await this.windowPresenter.createAppWindow({
             initialRoute: 'chat',
             x: newWindowOptions.x,
             y: newWindowOptions.y
           })
+        : await this.windowPresenter.createBrowserWindow({
+            x: newWindowOptions.x,
+            y: newWindowOptions.y
+          })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/tabPresenter.ts` around lines 928 - 948, The code chooses
the new window kind using getWindowType(originalWindowId) (sourceWindowType)
which breaks moves for non-local tabs; instead determine the intended window
type from the tab content being created (e.g., the createTab payload or the tab
object that contains allowNonLocal / content.windowType) and use that value when
deciding between createBrowserWindow and createAppWindow; update the branch that
calls this.windowPresenter.createBrowserWindow / createAppWindow to use the
tab-derived windowType (and preserve forMovedTab and x/y in newWindowOptions) so
attachTab receives a window of the correct kind and moved external tabs are not
rejected.
src/main/presenter/floatingButtonPresenter/index.ts (1)

363-377: ⚠️ Potential issue | 🔴 Critical

Make openMainWindow() async and await createAppWindow() call.

The createAppWindow() method is async and returns Promise<number | null> (line 503 in windowPresenter/index.ts), but line 376 calls it without await. This causes the promise rejection to bypass the try/catch block (resulting in an unhandled promise rejection), and the success log runs immediately before the window creation completes. Other call sites (sessionPresenter, tabPresenter) correctly await this method.

Suggested fix
-  private openMainWindow(): void {
+  private async openMainWindow(): Promise<void> {
     try {
       const windowPresenter = presenter.windowPresenter
       if (windowPresenter) {
         const mainWindow = windowPresenter.mainWindow
         if (mainWindow && !mainWindow.isDestroyed()) {
           if (mainWindow.isMinimized()) {
             mainWindow.restore()
           }
           mainWindow.show()
           mainWindow.focus()
           console.log('Main window opened from floating button context menu')
         } else {
-          windowPresenter.createAppWindow({ initialRoute: 'chat' })
+          const newWindowId = await windowPresenter.createAppWindow({ initialRoute: 'chat' })
+          if (newWindowId == null) {
+            console.warn('Failed to create a new main window from floating button context menu')
+            return
+          }
           console.log('Created new main window from floating button context menu')
         }
       }
     } catch (error) {
       console.error('Failed to open main window from floating button:', error)
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/floatingButtonPresenter/index.ts` around lines 363 - 377,
openMainWindow currently calls the async method
windowPresenter.createAppWindow(...) without awaiting it, causing unhandled
promise rejections and log ordering issues; change openMainWindow to be async
and await windowPresenter.createAppWindow({...}) inside the existing try/catch
so errors are caught and the "Created new main window" log runs after creation
completes; update any internal references if needed to handle the now-async
openMainWindow and ensure you await its callers where appropriate.
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)

30-35: ⚠️ Potential issue | 🟠 Major

webContentsId is still ignored during dispatch.

This rename stores per-window routing metadata, but sendInit, flushRender, and flushAll still emit STREAM_EVENTS.RESPONSE to SendTarget.ALL_WINDOWS. In multi-window mode every renderer will keep receiving every stream frame, which defeats the webContents migration.

🛠️ Possible direction
 export class StreamUpdateScheduler {
+  private sendResponse(state: SchedulerState, eventData: LLMAgentEventData): void {
+    if (state.webContentsId != null) {
+      void eventBus.sendToWebContents(state.webContentsId, STREAM_EVENTS.RESPONSE, eventData)
+      return
+    }
+
+    eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, SendTarget.ALL_WINDOWS, eventData)
+  }
+
   private sendInit(state: SchedulerState): void {
     state.hasSentInit = true
@@
-    eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, SendTarget.ALL_WINDOWS, eventData)
+    this.sendResponse(state, eventData)
   }
@@
-      eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, SendTarget.ALL_WINDOWS, eventData)
+      this.sendResponse(state, eventData)
@@
-      eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, SendTarget.ALL_WINDOWS, eventData)
+      this.sendResponse(state, eventData)

Also applies to: 56-70, 82-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts` around
lines 30 - 35, The scheduler currently ignores SchedulerState.webContentsId and
always dispatches STREAM_EVENTS.RESPONSE to SendTarget.ALL_WINDOWS; update
sendInit, flushRender, flushAll (and the other emit spots noted around lines
56-70 and 82-124) to check state.webContentsId and, when present, dispatch to
SendTarget.WINDOW with that webContentsId (otherwise fall back to
SendTarget.ALL_WINDOWS); locate dispatch/emitter calls in functions sendInit,
flushRender, flushAll and replace the fixed SendTarget.ALL_WINDOWS with a helper
or inline conditional that selects SendTarget.WINDOW + the numeric id from
SchedulerState.webContentsId.
src/main/presenter/sessionPresenter/managers/conversationManager.ts (1)

137-177: ⚠️ Potential issue | 🟠 Major

Emit a deactivate transition before overwriting an existing binding.

Both paths replace the entry for webContentsId without deactivating the conversation that was previously active on that renderer. The old conversation can stay marked active in renderer state until unrelated cleanup runs.

🛠️ Suggested fix
+    const previousConversationId = this.activeConversationBindings.get(webContentsId)
+    if (previousConversationId && previousConversationId !== conversationId) {
+      eventBus.sendToRenderer(CONVERSATION_EVENTS.DEACTIVATED, SendTarget.ALL_WINDOWS, {
+        webContentsId
+      })
+    }
     this.activeConversationBindings.set(webContentsId, conversationId)

Also applies to: 280-285

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/sessionPresenter/managers/conversationManager.ts` around
lines 137 - 177, When assigning a new active conversation, always emit
CONVERSATION_EVENTS.DEACTIVATED for any previous conversation bound to the same
webContents before overwriting the mapping; in setActiveConversation inspect
this.activeConversationBindings.get(webContentsId) (and the symmetric code at
the 280-285 path) and if a different previousConversationId exists call
eventBus.sendToRenderer(CONVERSATION_EVENTS.DEACTIVATED, SendTarget.ALL_WINDOWS,
{ webContentsId, /* optional: previousConversationId if payload expects it */ })
(or delete the old binding first) and then set the new binding and emit
ACTIVATED; update setActiveConversation (and the other replacement site) to
perform this deactivation step prior to calling
this.activeConversationBindings.set(webContentsId, conversationId) or replacing
entries.
src/main/presenter/windowPresenter/index.ts (1)

716-725: ⚠️ Potential issue | 🟡 Minor

Remove WINDOW_UNMAXIMIZED from the restore event handler.

Electron's restore event fires when a window is restored from a minimized state, not when it exits maximize state (that's handled by the separate unmaximize event). Sending WINDOW_UNMAXIMIZED when restore fires incorrectly tells the renderer the window is unmaximized, even if it was restored in a maximized state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/windowPresenter/index.ts` around lines 716 - 725, The
restore handler (handleRestore) is incorrectly sending
WINDOW_EVENTS.WINDOW_UNMAXIMIZED; remove the
appWindow.webContents.send(WINDOW_EVENTS.WINDOW_UNMAXIMIZED) call from the
handleRestore function and leave only the restore-specific logic (call
this.handleWindowRestore(windowId) and
eventBus.sendToMain(WINDOW_EVENTS.WINDOW_RESTORED, windowId)); ensure that
WINDOW_UNMAXIMIZED remains handled only by the unmaximize event listener so the
renderer receives unmaximized messages only on actual unmaximize events.
🟡 Minor comments (19)
src/renderer/src/i18n/fa-IR/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Fix the Persian label to say “window,” not “tab”.

Line 944 still says بستن برگه کنونی (“close current tab”), but this key is now closeWindow and the PR removes tab-based UI. That will show the wrong action to fa-IR users. Consider updating it to بستن پنجره کنونی (or your preferred Persian equivalent for “current window”).

💬 Suggested change
-    "closeWindow": "بستن برگه کنونی"
+    "closeWindow": "بستن پنجره کنونی"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/fa-IR/settings.json` at line 944, The Persian
translation for the "closeWindow" key is still using the word for "tab" ("بستن
برگه کنونی"); update the value for the closeWindow string (key: closeWindow) to
use the correct word for "window", e.g., replace the current text with "بستن
پنجره کنونی" (or another preferred Persian equivalent for "current window") so
the label matches the new window-based UI.
src/renderer/src/i18n/he-IL/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Fix the Hebrew label to say “window,” not “tab”.

closeWindow currently renders as "סגור את הכרטיסייה הנוכחית" ("Close the current tab"), which no longer matches the action name and will mislead users in the shortcuts UI. This should be translated as closing the current window instead. As per coding guidelines, "Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/he-IL/settings.json` at line 944, The translation for
the i18n key "closeWindow" is incorrect in he-IL (currently reads "סגור את
הכרטיסייה הנוכחית" = "Close the current tab"); update the he-IL value for the
"closeWindow" entry to a correct Hebrew phrase meaning "Close the current
window" (replace the tab wording), and ensure the same "closeWindow" key has
consistent translations added/verified across all locale files (da-DK, en-US,
fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) so the
shortcuts UI shows the correct wording in every language.
src/renderer/src/composables/usePageCapture.ts-186-195 (1)

186-195: ⚠️ Potential issue | 🟡 Minor

Fail fast when no usable capture target is resolved.

typeof id === 'number' still admits NaN, and if both lookups miss you only discover it later after the scroll loop has already mutated the page state. Validate these ids as finite integers and return an explicit error when captureTargets ends up empty.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/composables/usePageCapture.ts` around lines 186 - 195,
Replace the loose typeof checks with strict finite-integer validation for
webContentsId and windowId (e.g., use Number.isInteger or
Number.isFinite+Number.isInteger) when building captureTargets in
usePageCapture; ensure you only push ids that pass this validation and, if
captureTargets is empty after both checks, fail fast by returning or throwing an
explicit error describing that no valid capture target was resolved so the
caller stops before the scroll/mutation loop.
src/renderer/src/composables/usePageCapture.ts-186-186 (1)

186-186: ⚠️ Potential issue | 🟡 Minor

Keep inline comments in English.

These new comments are in Chinese inside a renderer TypeScript file. Please translate them to English to match the repository rule for logs/comments in this area.

As per coding guidelines, src/renderer/src/**/*.{ts,tsx,vue}: Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements.

Also applies to: 272-272

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/composables/usePageCapture.ts` at line 186, The inline
comment "// 优先使用当前 webContentsId;必要时回退到 windowId" (and the similar comment at
the other occurrence) must be translated to English to comply with the renderer
comments policy; locate the comments in
src/renderer/src/composables/usePageCapture.ts (around the logic that prefers
webContentsId over windowId, e.g., near variables or functions referencing
webContentsId and windowId) and replace the Chinese text with an English comment
such as "// Prefer the current webContentsId; fall back to windowId when
necessary" (apply the same translation for the other occurrence at the mentioned
location).
src/renderer/src/i18n/pt-BR/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Fix the PT-BR label to refer to a window, not a tab.

closeWindow is translated as Fechar a guia atual, which still says “current tab” and mislabels the new shortcut in Settings.

📝 Suggested fix
-    "closeWindow": "Fechar a guia atual"
+    "closeWindow": "Fechar a janela atual"

Based on learnings, "Use English (en-US) as the reference for translation accuracy when adding new keys."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/pt-BR/settings.json` at line 944, The PT-BR translation
for the key "closeWindow" is incorrect (reads "Fechar a guia atual" — "tab");
update the value for the "closeWindow" key to use the correct word for window
(e.g., "Fechar a janela atual") to match the English reference and new Settings
shortcut label; locate the "closeWindow" entry in the pt-BR settings.json and
replace the string accordingly.
src/renderer/src/composables/usePresenter.ts-4-4 (1)

4-4: ⚠️ Potential issue | 🟡 Minor

Keep source comments in English.

These new comments are in Chinese, which breaks the repo-wide rule for TS comments and makes this renderer code less consistent to maintain.

💬 Suggested wording
-// WebContentsId 缓存
+// Cached webContentsId
@@
-        // 获取当前 webContentsId
+        // Get the current webContentsId
@@
-        // 在调用中记录 webContentsId
+        // Log the webContentsId with the IPC call

As per coding guidelines, "**/*.{js,ts,tsx,jsx,vue}: Use English for all logs and comments".

Also applies to: 68-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/composables/usePresenter.ts` at line 4, Replace Chinese
comments in this file with English equivalents to meet repo guidelines: change
the comment "// WebContentsId 缓存" to something like "// WebContentsId cache" and
update any other Chinese comments in usePresenter (including the block around
lines 68-74) to clear English descriptions; search for occurrences of
"WebContentsId" and the exported function/usePresenter scope to locate and
replace all non-English comments.
docs/specs/multi-window-cleanup/tasks.md-5-10 (1)

5-10: ⚠️ Potential issue | 🟡 Minor

Update the checklist to match work already landed in this PR.

At least Lines 5 and 9 are already done in the files shown here (tsconfig.app.json and the locale shortcut updates). Leaving them unchecked makes the migration tracker misleading.

📝 Suggested update
-- [ ] Update build aliases and inputs
+- [x] Update build aliases and inputs
@@
-- [ ] Remove tab shortcuts from main/settings
+- [x] Remove tab shortcuts from main/settings
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/specs/multi-window-cleanup/tasks.md` around lines 5 - 10, The checklist
in docs/specs/multi-window-cleanup/tasks.md is out of date; mark the items that
were already completed in this PR as done (specifically check "[ ] Update build
aliases and inputs" and "[ ] Remove tab shortcuts from main/settings" to reflect
the tsconfig.app.json and locale shortcut updates), and update any remaining
items as appropriate so the migration tracker accurately reflects current work
(leave the other lines such as "Simplify browser renderer chrome", "Rewrite
YoBrowser window model", and "Split app-window and browser-window creation"
unchanged if still in progress).
src/renderer/src/i18n/fr-FR/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Use window wording in the FR shortcut label.

closeWindow is currently translated as Fermez la page d'onglet actuelle, which still points to a tab-based action instead of closing the current window.

📝 Suggested fix
-    "closeWindow": "Fermez la page d'onglet actuelle"
+    "closeWindow": "Fermer la fenêtre actuelle"

Based on learnings, "Use English (en-US) as the reference for translation accuracy when adding new keys."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/fr-FR/settings.json` at line 944, The French
translation for the key "closeWindow" uses tab-specific wording; update the
value of the "closeWindow" entry to reference a window instead of a tab (e.g.,
replace "Fermez la page d'onglet actuelle" with "Fermer la fenêtre actuelle" or
the preferred imperative "Fermez la fenêtre actuelle") and ensure it matches the
en-US source intent and existing project phrasing style.
src/renderer/src/i18n/da-DK/settings.json-890-890 (1)

890-890: ⚠️ Potential issue | 🟡 Minor

Translation mismatch: "fane" (tab) should be "vindue" (window).

The Danish translation "Luk aktiv fane" means "Close active tab", but the en-US reference is "Close the current window". The translation should use "vindue" instead of "fane" to accurately reflect the window-centric change.

Suggested fix
-    "closeWindow": "Luk aktiv fane"
+    "closeWindow": "Luk det aktuelle vindue"

Based on learnings: "Use English (en-US) as the reference for translation accuracy when adding new keys"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/da-DK/settings.json` at line 890, The translation for
the key "closeWindow" is incorrect: replace the Danish string "Luk aktiv fane"
(which means "Close active tab") with a window-centric phrase like "Luk det
aktuelle vindue" (or "Luk aktivt vindue") so it matches the en-US source "Close
the current window"; update the value for the "closeWindow" key accordingly.
src/main/eventbus.ts-147-151 (1)

147-151: ⚠️ Potential issue | 🟡 Minor

Fix forEach callback to not return a value.

The static analysis tool correctly flags that the forEach callback implicitly returns a value, which is ignored. Use a block body to make the intent clear.

🐛 Suggested fix
   broadcastToWebContents(webContentsIds: number[], eventName: string, ...args: unknown[]) {
-    webContentsIds.forEach((webContentsId) =>
-      this.sendToWebContents(webContentsId, eventName, ...args)
-    )
+    webContentsIds.forEach((webContentsId) => {
+      this.sendToWebContents(webContentsId, eventName, ...args)
+    })
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/eventbus.ts` around lines 147 - 151, The forEach callback in
broadcastToWebContents currently uses a concise arrow expression which
implicitly returns the result of this.sendToWebContents; change the callback to
a block-bodied function so it does not return a value (e.g., replace
webContentsIds.forEach((id) => this.sendToWebContents(...)) with
webContentsIds.forEach((id) => { this.sendToWebContents(...); });), keeping the
call to sendToWebContents intact and referencing the same webContentsId
parameter.
src/renderer/src/i18n/ja-JP/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Fix the Japanese closeWindow label.

This still says “close the current tab page”, so the shortcut text points back to the removed tab model. It should refer to the current window instead.

💬 Suggested translation
-    "closeWindow": "現在のタブページを閉じます"
+    "closeWindow": "現在のウィンドウを閉じます"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/ja-JP/settings.json` at line 944, The "closeWindow"
label currently reads "現在のタブページを閉じます" (close the current tab page); update the
value for the "closeWindow" key in settings.json to reference the window
instead, e.g. replace it with "現在のウィンドウを閉じます" so the shortcut text correctly
says "close the current window" (key: closeWindow).
src/renderer/src/i18n/ko-KR/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Translation inconsistent with window-centric terminology.

The key is closeWindow but the Korean translation "현재 탭 페이지를 닫습니다" means "Closes the current tab page". Given this PR removes tab concepts in favor of windows, the translation should reflect window terminology.

🌐 Suggested translation fix
-    "closeWindow": "현재 탭 페이지를 닫습니다"
+    "closeWindow": "현재 창을 닫습니다"

The suggested translation means "Closes the current window" in Korean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/ko-KR/settings.json` at line 944, The translation for
the key closeWindow is window-centric but currently reads as "closes the current
tab page"; update the value for closeWindow in settings.json to a window-focused
Korean phrase (e.g., "현재 창을 닫습니다") so the string matches the key and the new
window terminology used across the app.
src/main/presenter/index.ts-384-401 (1)

384-401: ⚠️ Potential issue | 🟡 Minor

Translate the updated IPC comments to English.

The new inline comments on Lines 384, 389, and 401 are still Chinese. Please keep this hunk consistent with the repo rule for English-only TS comments.

Suggested patch
-// IPC 主进程处理程序:动态调用 Presenter 的方法 (支持 window/webContents 上下文)
+// Main-process IPC handler: dynamically invoke Presenter methods with window/webContents context
 ipcMain.handle(
   'presenter:call',
   (event: IpcMainInvokeEvent, name: string, method: string, ...payloads: unknown[]) => {
     try {
-      // 构建调用上下文
+      // Build the call context
       const webContentsId = event.sender.id
       const windowId = BrowserWindow.fromWebContents(event.sender)?.id
@@
-      // 记录调用日志
+      // Log the IPC call
       if (import.meta.env.VITE_LOG_IPC_CALL === '1') {

As per coding guidelines, **/*.{js,ts,tsx,jsx,vue,mjs,cjs}: All logs and comments must be in English.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/index.ts` around lines 384 - 401, The inline comments in
the IPC handler for ipcMain.handle are still in Chinese; update them to English
to comply with repo rules. Locate the ipcMain.handle callback (the dynamic
Presenter call) and replace the Chinese comments around the call description,
context construction (near webContentsId and windowId assignments), and the log
comment with concise English equivalents that describe: "IPC main handler:
dynamically invoke Presenter methods (supports window/webContents context)",
"build call context" and "log the call" while keeping references to
IPCCallContext, webContentsId, windowId, presenterName and methodName intact.
src/renderer/src/i18n/ru-RU/settings.json-944-944 (1)

944-944: ⚠️ Potential issue | 🟡 Minor

Fix the Russian closeWindow label.

Закройте текущую страницу вкладки still means “close the current tab page”. This shortcut is now window-scoped, so the RU copy should refer to the current window instead.

Suggested patch
-    "closeWindow": "Закройте текущую страницу вкладки"
+    "closeWindow": "Закрыть текущее окно"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/ru-RU/settings.json` at line 944, The Russian label for
the i18n key closeWindow is inaccurate (it refers to a tab/page); update the
value for "closeWindow" in src/renderer/src/i18n/ru-RU/settings.json to a phrase
that references the current window (e.g., "Закрыть текущее окно" or similar),
ensuring the translation is concise and matches the UI intent of a window-scoped
shortcut.
src/renderer/src/components/WindowSideBar.vue-242-246 (1)

242-246: ⚠️ Potential issue | 🟡 Minor

Remove the stale windowId gate before opening settings.

openOrFocusSettingsWindow() no longer consumes a caller window id. The getWindowId() check on Lines 243-245 can still turn this click into a silent no-op even though the presenter call itself is valid.

Suggested patch
 const openSettings = () => {
-  const windowId = window.api.getWindowId()
-  if (windowId != null) {
-    void windowPresenter.openOrFocusSettingsWindow()
-  }
+  void windowPresenter.openOrFocusSettingsWindow()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 242 - 246, The
click handler openSettings currently checks window.api.getWindowId() and returns
early, which can silently prevent opening settings; remove the stale gate so
openSettings simply calls windowPresenter.openOrFocusSettingsWindow() (keeping
the void/async call pattern if needed) and delete the window.api.getWindowId()
conditional and related null check to ensure the presenter is always invoked.
src/shared/types/presenters/session.presenter.d.ts-109-112 (1)

109-112: ⚠️ Potential issue | 🟡 Minor

preferredWindowType is missing 'browser'.

SessionBindings.windowType already allows 'browser', but the new lookup API narrows callers back to 'main' | 'floating'. That makes the shared surface inconsistent and prevents browser-window callers from expressing the new binding type without casts.

🛠️ Minimal fix
   findWebContentsForSession(
     sessionId: string,
-    preferredWindowType?: 'main' | 'floating'
+    preferredWindowType?: 'main' | 'floating' | 'browser'
   ): Promise<number | null>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/types/presenters/session.presenter.d.ts` around lines 109 - 112,
The API for findWebContentsForSession narrows preferredWindowType to 'main' |
'floating' even though SessionBindings.windowType permits 'browser'; update the
signature of findWebContentsForSession(sessionId: string, preferredWindowType?:
'main' | 'floating') to accept the same union as SessionBindings.windowType
(include 'browser') so callers can pass browser window types without casts, and
ensure any call sites and the implementation of findWebContentsForSession handle
the 'browser' case consistently.
src/renderer/src/components/workspace/WorkspaceBrowserTabs.vue-47-52 (1)

47-52: ⚠️ Potential issue | 🟡 Minor

Don't permanently cache favicon load failures per window.

faviconError is keyed only by browserWindow.id (line 75), so once a favicon fails to load, subsequent navigations in that window skip favicon rendering entirely, even if the new page has a different favicon URL. Key the cache by (windowId, faviconUrl) or clear the entry when browserWindow.page.favicon changes.

Also, per Vue 3 Composition API guidelines, use reactive for objects instead of ref. Replace line 75 with:

const faviconError = reactive<Record<number, string>>({})

and track failed URLs per window to allow retry on different pages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/workspace/WorkspaceBrowserTabs.vue` around lines
47 - 52, The current favicon failure cache (faviconError) is keyed only by
browserWindow.id so a single failed load blocks all future favicons for that
window; change faviconError to a reactive map that keys failures by
windowId+faviconUrl (or store a per-window set of failed URLs) and update the
template/check to use the current browserWindow.page.favicon when deciding
whether to show the <img>; also replace the ref-based faviconError with a
reactive object (per reviewer: const faviconError = reactive<Record<number,
string>>({}) or a reactive<Record<string, true>>), set the failure entry in the
`@error` handler for the specific favicon URL (e.g. key =
`${browserWindow.id}:${browserWindow.page.favicon}`), and clear/remove the
relevant key when browserWindow.page.favicon changes so new favicons can retry.
src/renderer/browser/components/AppBar.vue-114-132 (1)

114-132: ⚠️ Potential issue | 🟡 Minor

Use named callback handlers and removeListener() instead of removeAllListeners().

The anonymous functions registered on lines 114–125 cannot be individually removed. Using removeAllListeners() on lines 129–132 unregisters every subscriber on those channels. If additional listeners are added to the same ipcRenderer events in the future, this code will inadvertently break them. Store the callback in a variable and use removeListener(channel, callback) in the cleanup hook instead, following the pattern used in other components like yoBrowser.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/browser/components/AppBar.vue` around lines 114 - 132, The event
handlers registered with ipcRenderer for WINDOW_EVENTS (WINDOW_MAXIMIZED,
WINDOW_UNMAXIMIZED, WINDOW_ENTER_FULL_SCREEN, WINDOW_LEAVE_FULL_SCREEN) use
anonymous callbacks and then call removeAllListeners in onBeforeUnmount, which
removes every subscriber; fix by creating named callback functions (e.g.
onWindowMaximized, onWindowUnmaximized, onWindowEnterFullScreen,
onWindowLeaveFullScreen) that set isMaximized.value and isFullscreened.value,
register those named handlers with ipcRenderer.on, and in onBeforeUnmount call
ipcRenderer.removeListener(channel, correspondingNamedHandler) for each channel
instead of removeAllListeners so only these listeners are removed.
src/main/presenter/windowPresenter/index.ts-545-545 (1)

545-545: ⚠️ Potential issue | 🟡 Minor

Keep new comments in English.

The doc comment added here is not in English.

As per coding guidelines, **/*.{js,ts,tsx,jsx,vue,mjs,cjs}: All logs and comments must be in English.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/windowPresenter/index.ts` at line 545, The doc comment
"创建一个新的兼容窗口包装器。" must be replaced with an English comment; locate the comment
above the function that creates a new compatible window wrapper (the comment
text currently in Chinese) and update it to an English docstring such as "Create
a new compatible window wrapper." ensuring all nearby comments and any TODOs are
also in English to follow the repository coding guidelines.
🧹 Nitpick comments (17)
src/main/presenter/windowPresenter/FloatingChatWindow.ts (1)

8-8: Consider renaming TAB_EVENTS for semantic consistency in window presenter context.

FloatingChatWindow uses TAB_EVENTS.RENDERER_TAB_READY (lines 197, 303), which is semantically odd since it's a window presenter, not a tab presenter. While the event works correctly, using tab-centric naming for window operations reduces naming clarity given the organized event system with separate WINDOW_EVENTS and TAB_EVENTS categories. Consider adding a window-specific renderer-ready event or using more semantically appropriate naming for this window presenter's lifecycle signals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/windowPresenter/FloatingChatWindow.ts` at line 8, The
FloatingChatWindow is using TAB_EVENTS.RENDERER_TAB_READY which is semantically
misleading for a window presenter; update the code to use a window-scoped event
instead—either add/emit a new WINDOW_EVENTS.RENDERER_WINDOW_READY constant and
replace uses of TAB_EVENTS.RENDERER_TAB_READY inside FloatingChatWindow, or
import and use an existing WINDOW_EVENTS variant; make changes where
FloatingChatWindow references TAB_EVENTS.RENDERER_TAB_READY (e.g., in event
emission and listeners around the renderer-ready logic) and ensure the
publisher/subscriber sides use the new WINDOW_EVENTS name consistently.
src/main/presenter/shortcutPresenter.ts (1)

26-27: Consider removing or gating debug console.log statements.

Multiple console.log statements appear to be debug artifacts. While logging is acceptable, these specific logs ('reg shortcuts', 'clean chat history', 'delete conversation', 'unreg shortcuts') seem like development-time debugging rather than production logging.

Suggested approach

Either remove these debug logs or use a conditional/structured logging approach:

-    console.log('reg shortcuts')
+    // Optionally use a debug flag or structured logger

Also applies to: 107-107, 112-112, 126-126, 152-152

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/shortcutPresenter.ts` around lines 26 - 27, Remove or gate
the stray debug console.log calls in shortcutPresenter (e.g., the "reg
shortcuts", "clean chat history", "delete conversation", "unreg shortcuts"
statements) — either delete them or replace them with a structured logger call
(e.g., processLogger.debug/processLogger.info) or wrap them behind a dev-only
flag (e.g., if (isDev) ...) so they don't pollute production output; update
occurrences near the functions handling shortcut registration/cleanup to use the
chosen logging strategy consistently.
src/main/presenter/configPresenter/shortcutKeySettings.ts (1)

5-6: Outdated comment references removed tab functionality.

The comment mentions "Register tab number shortcut keys (1-8)" but all tab-related shortcuts have been removed in this PR. Consider updating or removing this comment to avoid confusion.

Suggested fix
-// Register tab number shortcut keys (1-8) -> Fixed CommandKey+1 ~ CommandKey+8 to switch tabs
-// Below are regular shortcut key definitions
+// Shortcut key definitions for window and conversation management
 export const rendererShortcutKey = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/configPresenter/shortcutKeySettings.ts` around lines 5 -
6, The header comment in shortcutKeySettings.ts wrongly references "Register tab
number shortcut keys (1-8) -> Fixed CommandKey+1 ~ CommandKey+8 to switch tabs"
which no longer exists; update or remove that sentence so the file top comment
accurately describes the current regular shortcut key definitions in this module
(e.g., change to "Register regular shortcut key definitions" or delete the
obsolete tab-related line) and ensure any mention of tab shortcuts is removed
from the comment block near the top of the file.
src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts (4)

26-29: Error message should be in English.

The validation message on line 28 is in Chinese.

♻️ Suggested fix
   participants: z
     .array(ParticipantSchema)
-    .min(2, { message: '会议至少需要两位参会者。' })
+    .min(2, { message: 'A meeting requires at least two participants.' })
     .describe('参会者列表。'),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts` around
lines 26 - 29, The validation message for the participants array is written in
Chinese; update the schema definition that uses ParticipantSchema (the
participants field in meetingServer.ts) to use an English error message (e.g.,
"Meeting requires at least two participants.") by replacing the current .min(2,
{ message: '会议至少需要两位参会者。' }) with an equivalent .min(2, { message: 'Meeting
requires at least two participants.' }) so the validation message is in English.

67-69: Error message should be in English.

♻️ Suggested fix
       if (name !== 'start_meeting') {
-        throw new Error(`未知的工具: ${name}`)
+        throw new Error(`Unknown tool: ${name}`)
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts` around
lines 67 - 69, The thrown error message is in Chinese; change the Error thrown
in the branch that checks name !== 'start_meeting' (the if condition comparing
name to 'start_meeting') so it uses an English message like "Unknown tool:
${name}" instead of `未知的工具: ${name}` by updating the throw new Error(...) call
where the invalid tool name is handled.

85-94: Error message should be in English.

♻️ Suggested fix
       } catch (error) {
         return {
           content: [
             {
               type: 'text',
-              text: `会议启动失败: ${error instanceof Error ? error.message : String(error)}`
+              text: `Meeting startup failed: ${error instanceof Error ? error.message : String(error)}`
             }
           ],
           isError: true
         }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts` around
lines 85 - 94, The catch block in meetingServer.ts returns an error message in
Chinese; change the message to English (e.g., "Meeting failed to start: ...")
and keep the existing error extraction logic (error instanceof Error ?
error.message : String(error)) so the returned object (content array with type
'text' and isError: true) contains an English message; update the template
string accordingly where the current catch returns `会议启动失败: ${...}`.

19-22: Error messages should be in English.

The error message on line 21 is in Chinese. As per coding guidelines, all logs and comments must be in English.

♻️ Suggested fix
     {
-      message:
-        '错误:必须且只能通过 "tab_id" 或 "tab_title" 中的一个来指定参会者,两者不能同时提供,也不能都为空。'
+      message:
+        'Error: A participant must be specified by exactly one of "tab_id" or "tab_title", not both and not neither.'
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts` around
lines 19 - 22, Replace the Chinese error string in the object with an English
message; locate the object with the "message" property in meetingServer.ts (the
error object that currently reads '错误:必须且只能通过 "tab_id" 或 "tab_title"
中的一个来指定参会者,两者不能同时提供,也不能都为空。') and change it to a clear English message such as
"Error: Attendees must be specified by exactly one of 'tab_id' or 'tab_title';
do not provide both or neither." to conform to the project's English-only
logging/guidelines.
src/renderer/browser/stores/window.ts (2)

60-65: Consider clarifying the reload condition logic.

The condition changedWindowId === null || changedWindowId === windowId.value will reload state when the windowId couldn't be resolved from the payload. This might cause unnecessary reloads for events with malformed payloads. Consider being more explicit:

 const handleWindowEvent = async (_event: unknown, payload: unknown) => {
   const changedWindowId = resolveWindowId(payload)
-  if (changedWindowId === null || changedWindowId === windowId.value) {
+  // Reload only when the event affects this window
+  if (changedWindowId !== null && changedWindowId === windowId.value) {
     await loadState()
   }
 }

However, if the intent is to reload on all events where the target can't be determined (broadcast-style), keep the current logic but add a comment explaining the reasoning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/browser/stores/window.ts` around lines 60 - 65, The reload logic
in handleWindowEvent uses resolveWindowId(payload) and currently treats a null
result as a trigger to call loadState, which causes reloads for malformed
payloads; update the condition so loadState() is called only when
changedWindowId === windowId.value (targeted event) and, if you really intend
broadcast semantics, explicitly handle a well-defined broadcast marker (e.g.,
check for a specific payload flag) instead of null, or alternatively retain the
existing null-behavior but add a clear comment above handleWindowEvent
describing that null means "broadcast to all windows" and is intentionally used
to force reloads; reference resolveWindowId, windowId.value, handleWindowEvent,
and loadState when making the change.

67-80: Consider adding cleanup for IPC listeners.

The store subscribes to IPC events but doesn't provide a way to unsubscribe. While this may be acceptable for a singleton store that lives for the app's lifetime, adding a dispose method would improve testability and prevent potential memory leaks if the store is ever used in a more dynamic context.

♻️ Suggested addition
+  const dispose = () => {
+    if (!window?.electron?.ipcRenderer) return
+    WINDOW_EVENT_CHANNELS.forEach((channel) => {
+      window.electron.ipcRenderer.removeListener(channel, handleWindowEvent)
+    })
+  }
+
   return {
     windowId,
     browserWindow,
     page,
     isAboutBlank,
     init,
-    loadState
+    loadState,
+    dispose
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/browser/stores/window.ts` around lines 67 - 80, The init
function registers IPC listeners for WINDOW_EVENT_CHANNELS with
window.electron.ipcRenderer.on using handleWindowEvent but never removes them;
add a dispose (or teardown) method on the same store that iterates
WINDOW_EVENT_CHANNELS and calls window.electron.ipcRenderer.removeListener (or
off) with handleWindowEvent (guarding that window?.electron?.ipcRenderer exists)
and ensure tests or consumers can call dispose to unregister; keep init
unchanged except optionally exposing dispose alongside initialized, windowId,
and loadState so callers can clean up.
src/renderer/src/components/chat-input/SkillsIndicator.vue (1)

79-84: Remove the now-unused window.api dependency here too.

This method no longer needs a renderer-side windowId, so the preload check only adds another silent failure path before opening settings.

♻️ Suggested cleanup
 const openSettings = () => {
-  const windowId = window.api.getWindowId()
-  if (windowId != null) {
-    void windowPresenter.openOrFocusSettingsWindow()
-  }
+  void windowPresenter.openOrFocusSettingsWindow()
   panelOpen.value = false
 }

Based on learnings: Use usePresenter composable for main process communication instead of direct IPC calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/chat-input/SkillsIndicator.vue` around lines 79 -
84, The openSettings function still calls window.api.getWindowId(), which is now
unused and creates an unnecessary failure path; remove the window.api dependency
and simplify openSettings to call windowPresenter.openOrFocusSettingsWindow()
(via the usePresenter composable) unconditionally and then set panelOpen.value =
false, ensuring you delete the windowId variable and the surrounding null-check
so openSettings only references openOrFocusSettingsWindow and panelOpen.
src/renderer/src/pages/WelcomePage.vue (1)

79-83: Drop the stale windowId gate.

openOrFocusSettingsWindow() is already window-scoped, so this window.api.getWindowId() check only keeps an unnecessary preload dependency and can turn the button into a no-op when getWindowId() is null. Call the presenter directly here.

♻️ Suggested cleanup
 const onAddProvider = () => {
-  const windowId = window.api.getWindowId()
-  if (windowId != null) {
-    void windowPresenter.openOrFocusSettingsWindow()
-  }
+  void windowPresenter.openOrFocusSettingsWindow()
 }

Based on learnings: Use usePresenter composable for main process communication instead of direct IPC calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/pages/WelcomePage.vue` around lines 79 - 83, The
onAddProvider handler currently checks window.api.getWindowId() before calling
windowPresenter.openOrFocusSettingsWindow(), which creates an unnecessary
preload dependency and can make the button a no-op when getWindowId() is null;
remove the gate and call the presenter directly (replace the
window.api.getWindowId() check with a direct call to
windowPresenter.openOrFocusSettingsWindow()), and if the component is still
using direct IPC access, switch to the usePresenter composable for main-process
communication so the handler depends on the presenter API rather than
window.api.
vitest.config.renderer.ts (1)

28-28: Comment should be in English.

As per coding guidelines, all comments must be in English.

♻️ Suggested change
-    environment: 'jsdom', // 使用jsdom环境,适合renderer进程测试
+    environment: 'jsdom', // Use jsdom environment, suitable for renderer process tests
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vitest.config.renderer.ts` at line 28, The inline comment on the vitest
config line that reads "// 使用jsdom环境,适合renderer进程测试" must be translated to
English to follow project guidelines; update the comment adjacent to the
environment: 'jsdom' setting (in vitest.config.renderer.ts) to an English phrase
such as "// use jsdom environment, suitable for renderer process tests" or
equivalent, ensuring the meaning is preserved and no other code changes are
made.
src/main/presenter/sessionPresenter/types.ts (1)

54-66: Consider marking tabId as deprecated in the types.

Both CreateSessionOptions and CreateSessionParams now have both webContentsId and tabId fields. If tabId is kept for backward compatibility during the migration, consider adding a JSDoc @deprecated annotation to clarify which field should be used going forward.

📝 Suggested documentation
 export type CreateSessionOptions = {
   forceNewAndActivate?: boolean
   webContentsId?: number
+  /** `@deprecated` Use webContentsId instead */
   tabId?: number
 }

 export type CreateSessionParams = {
   title: string
   settings?: Partial<SessionConfig>
   webContentsId?: number
+  /** `@deprecated` Use webContentsId instead */
   tabId?: number
   options?: CreateSessionOptions
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/sessionPresenter/types.ts` around lines 54 - 66, Add JSDoc
`@deprecated` annotations to the tabId properties in both CreateSessionOptions and
CreateSessionParams to make it explicit they're legacy and that webContentsId
should be used going forward; update the comment text to state "use
webContentsId instead" and leave the properties in place for backward
compatibility while clearly marking them deprecated in the type declarations
(symbols: CreateSessionOptions.tabId, CreateSessionParams.tabId).
test/renderer/components/WindowSideBar.test.ts (1)

44-59: Consider returning yoBrowserStore from the setup function.

The new yoBrowserStore mock is correctly added but not included in the return statement on line 100. If future tests need to assert on openLatestOrCreate calls, it won't be accessible.

♻️ Suggested change
-  return { wrapper, operations, agentStore, sessionStore }
+  return { wrapper, operations, agentStore, sessionStore, yoBrowserStore }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/renderer/components/WindowSideBar.test.ts` around lines 44 - 59, The
test adds a mocked yoBrowserStore with openLatestOrCreate but never returns it
from the setup function, so tests cannot access or assert on openLatestOrCreate;
update the setup function to include yoBrowserStore in its returned object
(alongside agentStore, sessionStore, themeStore, etc.) so tests can reference
yoBrowserStore.openLatestOrCreate for assertions.
src/main/utils/index.ts (1)

10-26: Comments should be in English.

Multiple comments in this file are in Chinese, which violates coding guidelines. As per coding guidelines, all comments must be in English.

♻️ Suggested translations
-    // 查找目标窗口 (焦点窗口或第一个窗口)
+    // Find target window (focused window or first window)
     const targetWindow = presenter.windowPresenter.getFocusedWindow() || allWindows[0]

     if (!targetWindow.isDestroyed()) {
-      // 逻辑: 如果窗口可见且不是从托盘点击触发,则隐藏;否则显示并置顶
+      // Logic: If window is visible and not triggered from tray click, hide it; otherwise show and bring to front
       if (targetWindow.isVisible() && !mustShow) {
         presenter.windowPresenter.hide(targetWindow.id)
       } else {
         presenter.windowPresenter.show(targetWindow.id)
-        targetWindow.focus() // 确保窗口置顶
+        targetWindow.focus() // Ensure window is brought to front
       }
     } else {
-      console.warn('Target window for SHOW_HIDDEN_WINDOW event is destroyed.') // 保持 warn
-      // 如果目标窗口已销毁,创建新窗口
+      console.warn('Target window for SHOW_HIDDEN_WINDOW event is destroyed.')
+      // If target window is destroyed, create a new window
       presenter.windowPresenter.createAppWindow({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/utils/index.ts` around lines 10 - 26, The comments in this block are
written in Chinese; update them to English while preserving the original intent
and clarity. Replace the Chinese comment lines around the logic that selects
targetWindow (presenter.windowPresenter.getFocusedWindow() || allWindows[0]),
the isDestroyed() check, the visibility/mustShow logic (targetWindow.isVisible()
&& !mustShow), the hide/show calls
(presenter.windowPresenter.hide(targetWindow.id) and
presenter.windowPresenter.show(targetWindow.id)), the focus call
(targetWindow.focus()), the console.warn message, and the fallback that calls
presenter.windowPresenter.createAppWindow({ initialRoute: 'chat'}) with concise
English comments describing each step (select target window, check destroyed
state, hide if visible and not mustShow, otherwise show and focus, warn if
destroyed and create new window).
src/main/presenter/lifecyclePresenter/hooks/after-start/windowCreationHook.ts (1)

41-48: Make the final startup log branch-accurate.

When windows already exist, Lines 42 and 48 together say both “skipping initial window creation” and “initial application window created successfully”. Reword the final log or keep the success message inside the creation branch only.

Suggested patch
-    console.log('windowCreationHook: Initial application window created successfully')
+    console.log('windowCreationHook: Window creation hook completed successfully')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/presenter/lifecyclePresenter/hooks/after-start/windowCreationHook.ts`
around lines 41 - 48, The final startup log is misleading because it prints
"Initial application window created successfully" even when the else branch
skips creation; update the logging so it’s branch-accurate: either move the
success log into the branch that actually creates windows (the block that runs
when windows are created) or change the final console.log after
presenter.shortcutPresenter.registerShortcuts() to a neutral message (e.g.,
"Application startup complete" or "Window creation skipped, application ready")
so the messages reflect the actual path taken; locate this behavior around the
windowCreationHook and the console.log statements that mention "skipping initial
window creation" and "Initial application window created successfully".
src/renderer/src/stores/yoBrowser.ts (1)

18-18: Use shallowRef for the window snapshot.

BrowserWindowInfo[] is loaded as a whole snapshot and replaced wholesale in loadState(). Deep-proxying every nested window/page object here adds reactivity cost without buying finer-grained updates.

♻️ Minimal refactor
-import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
+import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
@@
-  const windows = ref<BrowserWindowInfo[]>([])
+  const windows = shallowRef<BrowserWindowInfo[]>([])

As per coding guidelines, "Use shallowRef and shallowReactive for optimizing reactivity with large objects".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/stores/yoBrowser.ts` at line 18, The windows state is
currently a deep ref (const windows = ref<BrowserWindowInfo[]>([])) which
needlessly deep-proxies the whole snapshot; change it to a shallowRef so
replacing the entire array in loadState() doesn't create deep reactivity
overhead: replace ref with shallowRef for the windows variable (import
shallowRef from 'vue' if not already) and keep the BrowserWindowInfo[] type,
ensuring loadState() assigns the new snapshot to windows.value.

Comment on lines +269 to +270
await this.ensureChatWindowReady(windowId)
eventBus.sendToRenderer(DEEPLINK_EVENTS.START, SendTarget.DEFAULT_WINDOW, {
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

Send the deeplink to the window you just prepared.

After resolving and waiting on a specific windowId, Line 270 still dispatches to SendTarget.DEFAULT_WINDOW. In multi-window mode that can deliver the payload to a different renderer than the one that was just shown/focused.

🛠️ Minimal fix
-    eventBus.sendToRenderer(DEEPLINK_EVENTS.START, SendTarget.DEFAULT_WINDOW, {
+    eventBus.sendToWindow(DEEPLINK_EVENTS.START, windowId, {
       msg,
       modelId,
       systemPrompt,
       mentions,
       autoSend
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/deeplinkPresenter/index.ts` around lines 269 - 270, The
deeplink is being sent to SendTarget.DEFAULT_WINDOW after awaiting
ensureChatWindowReady(windowId), which can route the payload to the wrong
renderer in multi-window mode; change the call to
eventBus.sendToRenderer(DEEPLINK_EVENTS.START, windowId, { ... }) (or use the
API variant that targets a specific window) so the deeplink is delivered to the
specific windowId you just prepared (refer to ensureChatWindowReady,
eventBus.sendToRenderer, DEEPLINK_EVENTS.START and SendTarget.DEFAULT_WINDOW).

Comment on lines +123 to +135
private focusBoundWebContents(webContentsId: number): void {
const targetContents = electronWebContents.fromId(webContentsId)
if (!targetContents || targetContents.isDestroyed()) {
return
}

if (existingTabId !== null && existingTabId !== tabId) {
const targetWindow = BrowserWindow.fromWebContents(targetContents)
if (!targetWindow || targetWindow.isDestroyed()) {
return
}

presenter.windowPresenter.show(targetWindow.id, true)
}
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

Handle the floating chat window separately when refocusing a bound conversation.

BrowserWindow.fromWebContents() can resolve to the floating chat window, but windowPresenter.show(targetWindow.id, true) only works for IDs stored in WindowPresenter.windows. In that case this path becomes a no-op instead of surfacing the existing conversation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/sessionPresenter/managers/conversationManager.ts` around
lines 123 - 135, The refocus path can hit a floating chat BrowserWindow that
isn't tracked in WindowPresenter.windows causing
presenter.windowPresenter.show(targetWindow.id, true) to be a no-op; update
focusBoundWebContents to detect whether targetWindow.id exists in
presenter.windowPresenter.windows (or equivalent registry) and if it is not
managed, fall back to directly making the floating window visible and focused
(e.g., call targetWindow.show() and targetWindow.focus() or use the
windowPresenter's floating-window API if one exists) instead of only calling
presenter.windowPresenter.show(targetWindow.id, true).

Comment on lines +503 to +518
public async createAppWindow(options?: {
initialRoute?: string
x?: number
y?: number
}): Promise<number | null> {
return await this.createManagedWindow({
initialTab: {
url:
options?.initialRoute === 'chat' || !options?.initialRoute
? 'local://chat'
: `local://${options.initialRoute}`
},
windowType: 'chat',
x: options?.x,
y: options?.y
})
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

Wire initialRoute through to the chat renderer load path.

createAppWindow() stores the requested route in initialTab.url, but the chat branch of createManagedWindow() always loads #/chat. Any caller passing initialRoute !== 'chat' still lands on chat, so the new option is effectively a no-op.

Also applies to: 845-857

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/windowPresenter/index.ts` around lines 503 - 518,
createAppWindow sets initialTab.url but createManagedWindow's chat branch always
loads '#/chat', so initialRoute is ignored; update createManagedWindow (the
chat/window loading code path) to parse/use the provided initialTab.url (or
fallback to 'local://chat') to construct the renderer load path (i.e., replace
hardcoded '#/chat' with a hash/route derived from initialTab.url) so callers
that pass initialRoute != 'chat' actually land on that route; apply the same
change to the other chat-load site referenced around the 845-857 area.

Comment on lines +571 to 574
const managedWindowState = windowStateManager({
defaultWidth,
defaultHeight
})
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 examine the file in question and get context around the specified lines
wc -l src/main/presenter/windowPresenter/index.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 113


🏁 Script executed:

# Read the file with context around lines 571-574
sed -n '550,600p' src/main/presenter/windowPresenter/index.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 1507


🏁 Script executed:

# Search for windowStateManager definition and imports
rg "windowStateManager" -B 2 -A 5 src/main/presenter/windowPresenter/

Repository: ThinkInAIXYZ/deepchat

Length of output: 2040


🏁 Script executed:

# Check what parameters electron-window-state accepts
rg "electron-window-state" src/main/presenter/windowPresenter/

Repository: ThinkInAIXYZ/deepchat

Length of output: 187


Persist chat and browser window state in separate files.

Both window types currently use the default electron-window-state file. After one window saves its bounds, the other type restores those bounds too, so the 800x620 vs 600x620 defaults stop applying after the first saved window.

🛠️ Suggested fix
     const managedWindowState = windowStateManager({
+      file: windowType === 'browser' ? 'browser-window-state.json' : 'app-window-state.json',
       defaultWidth,
       defaultHeight
     })
📝 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
const managedWindowState = windowStateManager({
defaultWidth,
defaultHeight
})
const managedWindowState = windowStateManager({
file: windowType === 'browser' ? 'browser-window-state.json' : 'app-window-state.json',
defaultWidth,
defaultHeight
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/windowPresenter/index.ts` around lines 571 - 574, The
windowStateManager call (managedWindowState) is using the same default state
file for both chat and browser windows, causing one window's saved bounds to be
applied to the other; update the windowStateManager initialization to pass a
distinct state filename per window type (e.g., derive a unique file name or key
from the window type such as "chat-window-state" vs "browser-window-state") when
creating managedWindowState so each window type loads/saves its own bounds;
locate the calls to windowStateManager/managedWindowState (the place where
defaultWidth/defaultHeight are passed) and add the unique file option or key so
chat and browser windows persist separately.


@source '../**/*.{vue,ts,tsx,js,jsx}';
@source '../../shell/**/*.{vue,ts,tsx,js,jsx}';
@source '../../browser/**/*.{vue,ts,tsx,js,jsx}';
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:

#!/bin/bash
set -euo pipefail

echo "=== Finding lint configuration files ==="
fd -HI 'biome.*\.json|\.stylelintrc.*|stylelint.*\.(js|cjs|mjs|json)' 2>/dev/null || echo "No pattern matches found"

echo ""
echo "=== Checking for `@source`, ignoreAtRules, or tailwindcss references ==="
rg -n '@source|ignoreAtRules|tailwindcss' \
  --glob 'biome*.json' \
  --glob '.stylelintrc*' \
  --glob 'stylelint*.{js,cjs,mjs,json}' \
  --glob 'package.json' \
  2>/dev/null || echo "No matches found"

echo ""
echo "=== Checking the actual style.css file ==="
cat -n src/renderer/src/assets/style.css 2>/dev/null || echo "File not found"

Repository: ThinkInAIXYZ/deepchat

Length of output: 40970


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .stylelintrc.json ==="
cat .stylelintrc.json

echo ""
echo "=== biome.json ==="
cat biome.json

Repository: ThinkInAIXYZ/deepchat

Length of output: 2891


Configure CSS tooling to recognize Tailwind v4 at-rules.

The .stylelintrc.json extends stylelint-config-standard-scss, which includes the at-rule-no-unknown rule that will flag @source, @plugin, and @theme directives as invalid. Similarly, Biome's CSS linter has no Tailwind v4 support and will reject these directives. Update the Stylelint config to ignore Tailwind-specific at-rules:

{
  "rules": {
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": [
          "source",
          "plugin",
          "theme",
          "layer",
          "apply",
          "custom-variant"
        ]
      }
    ]
  }
}

Consider also extending or configuring Biome to suppress CSS linting errors for Tailwind directives, or disable CSS linting if Biome doesn't fully support Tailwind v4 yet.

🧰 Tools
🪛 Biome (2.4.4)

[error] 9-9: Tailwind-specific syntax is disabled.

(parse)

🪛 Stylelint (17.4.0)

[error] 9-9: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/assets/style.css` at line 9, The CSS linter is flagging
Tailwind v4 at-rules like `@source/`@plugin/@theme in
src/renderer/src/assets/style.css; update the Stylelint configuration (the
.stylelintrc.json that currently extends stylelint-config-standard-scss) to
allow Tailwind at-rules by adding an at-rule-no-unknown rule with ignoreAtRules
for "source", "plugin", "theme", "layer", "apply", and "custom-variant"; also
adjust Biome’s CSS lint settings (or disable CSS linting in Biome) to similarly
ignore or permit these Tailwind directives so the `@source` line in style.css is
not rejected.

Comment on lines +162 to +164
clearActiveConversationBinding(webContentsId: number): Promise<void>
/** @deprecated Use clearActiveConversationBinding instead. */
clearActiveThread(webContentsId: number): Promise<void>
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 look at the file structure and locate the relevant files
find . -type f -name "*.presenter*" | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 594


🏁 Script executed:

# Read the thread presenter type definition file
cat -n src/shared/types/presenters/thread.presenter.d.ts | head -200

Repository: ThinkInAIXYZ/deepchat

Length of output: 8160


🏁 Script executed:

# Also search for the implementation file
find . -type f -name "*thread.presenter*" ! -name "*.d.ts" 2>/dev/null | head -10

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Let's search for clearActiveThread and clearActiveConversationBinding to understand the migration
rg "clearActiveThread|clearActiveConversationBinding" --type ts --type tsx -B 2 -A 2

Repository: ThinkInAIXYZ/deepchat

Length of output: 92


🏁 Script executed:

# Search for implementation files (not .d.ts)
find . -type f -path "*/presenters/*thread*" ! -name "*.d.ts" 2>/dev/null

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Search for any references to tabId vs webContentsId to understand the relationship
rg "tabId|webContentsId" --type ts -B 1 -A 1 | head -100

Repository: ThinkInAIXYZ/deepchat

Length of output: 7421


🏁 Script executed:

# Look for implementation of clearActiveThread or clearActiveConversationBinding
rg "clearActiveThread|clearActiveConversationBinding" --type ts -B 3 -A 5 2>/dev/null | head -150

Repository: ThinkInAIXYZ/deepchat

Length of output: 7742


🏁 Script executed:

# Verify the legacy interface to confirm original parameter name
cat -n src/shared/types/presenters/legacy.presenters.d.ts | grep -A 2 -B 2 "clearActiveThread"

Repository: ThinkInAIXYZ/deepchat

Length of output: 357


🏁 Script executed:

# Check how other deprecated aliases handle parameter naming in session.presenter
cat -n src/shared/types/presenters/session.presenter.d.ts | grep -B 3 -A 3 "tabId"

Repository: ThinkInAIXYZ/deepchat

Length of output: 1194


Deprecated alias should preserve the original tabId parameter name.

The original clearActiveThread method accepted tabId (as shown in legacy.presenters.d.ts:1217), but the current deprecated version at line 164 changes the parameter to webContentsId. This breaks the deprecation pattern used elsewhere in the codebase (see session.presenter.d.ts where deprecated methods preserve old parameter names like tabId). Existing callers using the deprecated method become confused about parameter semantics. Either keep the deprecated signature as clearActiveThread(tabId: number) for full backward compatibility, or remove it in a breaking change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/types/presenters/thread.presenter.d.ts` around lines 162 - 164,
The deprecated alias clearActiveThread currently uses parameter name
webContentsId which differs from the original API; update its signature to
preserve the original parameter name by changing
clearActiveThread(webContentsId: number) to clearActiveThread(tabId: number)
(matching the legacy.presenters.d.ts usage) so callers see the old parameter
name for backward compatibility; ensure the implementation still forwards to
clearActiveConversationBinding and retains deprecation comment.

@zerob13 zerob13 merged commit f4165c7 into dev Mar 7, 2026
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.

1 participant