fix(desktop): prevent browser webview reload on tab/workspace switch#2
fix(desktop): prevent browser webview reload on tab/workspace switch#2
Conversation
Electron's <webview> tag reloads its content whenever the element is reparented in the DOM. The previous approach rendered only the active tab, causing BrowserPane to unmount on every tab switch and park the webview in a hidden container (DOM reparent) — triggering a hard reload. - Add PersistentTabRenderer that keeps all workspace tabs mounted and hides inactive ones via off-screen positioning (not display:none, which stops Electron's compositor) - Wrap each webview in a persistent wrapper div so the webview's parentNode never changes during park/reclaim cycles - Use unique Mosaic IDs per tab to prevent drag-drop conflicts between simultaneously mounted Mosaic instances
Keep workspace pages mounted across workspace switches so that webview elements are never removed from the DOM. - Add KeepAliveWorkspaces component that replaces <Outlet /> for workspace routes, rendering all visited workspaces simultaneously and hiding inactive ones off-screen - Make WorkspacePage accept workspaceId as a prop override so it works outside the router's matched-route context - Thread workspaceId through WorkspaceLayout → ContentView → TabsContent via props instead of useParams(), preventing hidden workspaces from reading the wrong workspace ID from the active route
…p-alive When multiple WorkspacePage instances are mounted simultaneously (KeepAliveWorkspaces), several issues occurred: 1. Components using useParams() read the ACTIVE workspace's ID instead of their own — causing wrong files, wrong sidebar data, and wrong tab lists. Fixed by introducing WorkspaceIdContext and replacing useParams() with useWorkspaceId() in 9 affected components. 2. Mosaic drag-drop was broken because GroupItem used a static mosaicId that no longer matched the per-tab dynamic ID. Fixed by computing the active tab's mosaicId dynamically. 3. All workspace hotkeys fired for every mounted workspace. Fixed by passing `enabled: isActive` to all useAppHotkey calls so only the active workspace responds to keyboard shortcuts.
Electron's <webview> tags consume mouse events in their guest process, so mouse back/forward buttons (button 3/4) never reach the host renderer's event listeners. Handle the `app-command` event on the main BrowserWindow to intercept `browser-backward` and `browser-forward` commands, forwarding them to the focused webview's navigation history. Also update usePresetHotkeys to accept an options parameter for consistency with the enabled-flag pattern used across workspace hotkeys.
The `app-command` event is Windows/Linux only — it never fires on macOS. Instead, inject a mouse event listener into the webview guest page via executeJavaScript on every `dom-ready`. The injected script calls `history.back()` / `history.forward()` directly inside the guest when mouse buttons 3/4 are pressed. The existing `app-command` handler in the main process is kept as a fallback for Windows/Linux.
…spaces PersistentTabRenderer now only keeps tabs mounted when they contain a webview pane. Tabs with only terminals, chat, or file viewers unmount normally on tab switch, reducing memory usage. KeepAliveWorkspaces now watches the workspace list from the database and automatically removes deleted workspaces from the keep-alive set, preventing stale WorkspacePage instances from lingering in memory.
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 44 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (22)
📝 WalkthroughWalkthroughAdds a WorkspaceId React context, converts many workspace-related components to consume workspaceId from context, implements keep-alive mounting for workspace pages and tabs that host webviews, persists webview DOM wrappers for reparenting, and forwards Electron app back/forward commands to the focused guest webContents. Changes
Sequence Diagram(s)sequenceDiagram
participant Router as TanStack Router
participant DashLayout as DashboardLayout
participant KeepAlive as KeepAliveWorkspaces
participant WSPage as WorkspacePage
participant WSContext as WorkspaceIdContext
participant Child as Child Components
Router->>DashLayout: route activates dashboard
DashLayout->>KeepAlive: render KeepAliveWorkspaces (replaces Outlet)
KeepAlive->>KeepAlive: detect /workspace/$id, add to visitedIds
KeepAlive->>WSPage: render WorkspacePage for each visitedId
WSPage->>WSContext: provide workspaceId
Child->>WSContext: call useWorkspaceId()
Child->>Child: fetch data & register hotkeys using workspaceId
sequenceDiagram
participant Electron as Electron Main
participant FocusFinder as Focus Finder
participant Guest as Guest WebContents
participant History as Guest NavigationHistory
Electron->>Electron: receives app-command(browser-backward/forward)
Electron->>FocusFinder: enumerate all webContents to find focused guest
FocusFinder->>Guest: identify focused guest webContents
Electron->>History: call guest.navigationHistory.goBack()/goForward()
History->>Guest: guest navigates back/forward
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Keep both our useWorkspaceId import and main's new githubQueryPolicy imports. The useParams import was already removed by our branch.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/FilesView.tsx (1)
144-148: Dead fallback code after migration touseWorkspaceId().Since
useWorkspaceId()throws when called outside a provider (never returnsundefined), the?? ""fallback on line 146 and theenabled: !!workspaceIdguard on line 147 are now effectively dead code. The same applies to lines 371 and 420.This isn't a bug, but the defensive checks are misleading. Consider removing them for clarity, or if you want to keep defensive coding for potential future refactoring, add a comment explaining the intent.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/FilesView.tsx` around lines 144 - 148, The code uses useWorkspaceId() which never returns undefined (it throws outside a provider), so remove the dead fallback and redundant enabled guard around the query: update the electronTrpc.workspaces.get.useQuery call to pass workspaceId directly (no "?? ''") and drop the { enabled: !!workspaceId } option; alternatively, if you intentionally want to keep defensive checks, add a short comment next to useWorkspaceId(), workspaceId, or the electronTrpc.workspaces.get.useQuery invocation explaining that the fallback and enabled guard are retained intentionally for future refactors.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx (1)
35-35: Dead guards after migration touseWorkspaceId().Similar to other migrated components, the null/undefined guards for
activeWorkspaceIdare now dead code sinceuseWorkspaceId()throws rather than returningundefined. This includes:
- Line 54:
activeWorkspaceId ?? ""- Line 55:
enabled: !!activeWorkspaceId- Lines 113, 120, 222, 227, 232, 238, 249, 272: Various
if (!activeWorkspaceId)guardsNot a bug, but these guards are now misleading.
Also applies to: 53-56
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx` at line 35, Remove the now-dead null/undefined guards added for activeWorkspaceId (which is returned by useWorkspaceId() and will throw instead of returning undefined); specifically, eliminate usages like "activeWorkspaceId ?? ''", boolean coercions "enabled: !!activeWorkspaceId", and all "if (!activeWorkspaceId)" branches in GroupStrip (references: activeWorkspaceId, useWorkspaceId, GroupStrip component) and either simplify to use activeWorkspaceId directly or replace with explicit non-null assumptions/assertions where necessary so the logic is not misleading.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx (1)
130-131: Dead fallback after migration touseWorkspaceId().Since
useWorkspaceId()throws when called outside a provider rather than returningundefined, the?? worktreePathfallback on line 131 is now dead code. The same applies to other null-coalescing operators and boolean guards forworkspaceIdthroughout this file (e.g., lines 360, 409, 444).The previous behavior with
useParams({ strict: false })allowedworkspaceIdto be undefined, making the fallback meaningful. Now, the component simply crashes if not wrapped inWorkspaceIdProvider.♻️ Suggested simplification
const workspaceId = useWorkspaceId(); - const normalizedWorkspaceId = workspaceId ?? worktreePath; + const normalizedWorkspaceId = workspaceId;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx` around lines 130 - 131, The null-coalescing fallback using worktreePath is dead because useWorkspaceId() now throws when no provider exists; remove the unnecessary fallback and guards: change the normalizedWorkspaceId declaration to simply use the provider value (keep "const workspaceId = useWorkspaceId();" and remove "const normalizedWorkspaceId = workspaceId ?? worktreePath;"), replace any uses of "normalizedWorkspaceId" or expressions like "workspaceId ?? worktreePath" with "workspaceId", and remove boolean checks that branch on workspaceId being undefined (e.g., any conditional guards or ternaries expecting undefined), relying on the provider guarantee instead.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx (1)
28-37: Consider defensive null-check fortab.layout.The
extractPaneIdsFromLayoutfunction (per context snippet) doesn't handlenull/undefinedlayouts. WhileTab.layoutis documented as "Always defined," a defensive check would prevent runtime errors if this invariant is ever violated.🛡️ Optional defensive check
const tabsWithWebview = useMemo(() => { const ids = new Set<string>(); for (const tab of tabs) { + if (!tab.layout) continue; const paneIds = extractPaneIdsFromLayout(tab.layout); if (paneIds.some((id) => panes[id]?.type === "webview")) { ids.add(tab.id); } } return ids; }, [tabs, panes]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx` around lines 28 - 37, tabsWithWebview calls extractPaneIdsFromLayout(tab.layout) without guarding against tab.layout being null/undefined; add a defensive check in the useMemo loop (in PersistentTabRenderer) so you only call extractPaneIdsFromLayout when tab.layout is non-null/undefined (or compute an empty array/skip that tab otherwise), and continue using panes[id]?.type to detect "webview" — reference the tabsWithWebview computation, the tab.layout property, and extractPaneIdsFromLayout to locate the change.apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/KeepAliveWorkspaces.tsx (1)
27-35: Consider bounding the number of kept-alive workspaces.The
visitedIdslist grows unboundedly as users navigate between workspaces. While deleted workspaces are evicted, frequently switching between many existing workspaces could accumulate significant memory usage (each mountedWorkspacePageincludes its own state, queries, and potentially webviews).A maximum keep-alive count (e.g., 5-10 most recent) could limit memory footprint while preserving the core benefit.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/KeepAliveWorkspaces.tsx` around lines 27 - 35, The visitedIds/visitedSetRef list grows unboundedly; add a MAX_KEEP_ALIVE (e.g., const MAX_KEEP_ALIVE = 5) and trim oldest entries when adding a new activeWorkspaceId. In the useEffect that watches activeWorkspaceId, when a new id is added to visitedSetRef and setVisitedIds, enforce: push the new id to the end of the ordered visitedIds array, and if length > MAX_KEEP_ALIVE remove the earliest id(s) from both the array and visitedSetRef before calling setVisitedIds(Array.from(...)) so visitedIds (state) preserves recency order while visitedSetRef (ref) stays in sync. Ensure you update both visitedIds and visitedSetRef in the same effect to avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx:
- Around line 117-118: Remove the redundant declaration const genericNavigate =
useNavigate(); and consolidate to a single useNavigate() call (const navigate =
useNavigate()); then replace all references to genericNavigate with navigate
(e.g., in the navigation calls around the workspace page handlers) so there is
only one navigate variable used throughout the component.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts`:
- Around line 201-205: The guest mouse-nav shim is being injected
unconditionally inside handleDomReady via webview.executeJavaScript; change that
so the executeJavaScript call that injects the shim only runs when
PLATFORM.IS_MAC is true (gate the injection with PLATFORM.IS_MAC), leaving the
existing cleanup logic as-is; locate the injection by searching for
handleDomReady and the executeJavaScript call that loads the guest mouse-nav
shim and wrap/guard that call with PLATFORM.IS_MAC so Windows/Linux no longer
run the shim.
---
Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/KeepAliveWorkspaces.tsx`:
- Around line 27-35: The visitedIds/visitedSetRef list grows unboundedly; add a
MAX_KEEP_ALIVE (e.g., const MAX_KEEP_ALIVE = 5) and trim oldest entries when
adding a new activeWorkspaceId. In the useEffect that watches activeWorkspaceId,
when a new id is added to visitedSetRef and setVisitedIds, enforce: push the new
id to the end of the ordered visitedIds array, and if length > MAX_KEEP_ALIVE
remove the earliest id(s) from both the array and visitedSetRef before calling
setVisitedIds(Array.from(...)) so visitedIds (state) preserves recency order
while visitedSetRef (ref) stays in sync. Ensure you update both visitedIds and
visitedSetRef in the same effect to avoid drift.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx`:
- Line 35: Remove the now-dead null/undefined guards added for activeWorkspaceId
(which is returned by useWorkspaceId() and will throw instead of returning
undefined); specifically, eliminate usages like "activeWorkspaceId ?? ''",
boolean coercions "enabled: !!activeWorkspaceId", and all "if
(!activeWorkspaceId)" branches in GroupStrip (references: activeWorkspaceId,
useWorkspaceId, GroupStrip component) and either simplify to use
activeWorkspaceId directly or replace with explicit non-null
assumptions/assertions where necessary so the logic is not misleading.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx`:
- Around line 28-37: tabsWithWebview calls extractPaneIdsFromLayout(tab.layout)
without guarding against tab.layout being null/undefined; add a defensive check
in the useMemo loop (in PersistentTabRenderer) so you only call
extractPaneIdsFromLayout when tab.layout is non-null/undefined (or compute an
empty array/skip that tab otherwise), and continue using panes[id]?.type to
detect "webview" — reference the tabsWithWebview computation, the tab.layout
property, and extractPaneIdsFromLayout to locate the change.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx`:
- Around line 130-131: The null-coalescing fallback using worktreePath is dead
because useWorkspaceId() now throws when no provider exists; remove the
unnecessary fallback and guards: change the normalizedWorkspaceId declaration to
simply use the provider value (keep "const workspaceId = useWorkspaceId();" and
remove "const normalizedWorkspaceId = workspaceId ?? worktreePath;"), replace
any uses of "normalizedWorkspaceId" or expressions like "workspaceId ??
worktreePath" with "workspaceId", and remove boolean checks that branch on
workspaceId being undefined (e.g., any conditional guards or ternaries expecting
undefined), relying on the provider guarantee instead.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/FilesView.tsx`:
- Around line 144-148: The code uses useWorkspaceId() which never returns
undefined (it throws outside a provider), so remove the dead fallback and
redundant enabled guard around the query: update the
electronTrpc.workspaces.get.useQuery call to pass workspaceId directly (no "??
''") and drop the { enabled: !!workspaceId } option; alternatively, if you
intentionally want to keep defensive checks, add a short comment next to
useWorkspaceId(), workspaceId, or the electronTrpc.workspaces.get.useQuery
invocation explaining that the fallback and enabled guard are retained
intentionally for future refactors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0f446cd0-c646-448a-8e66-c676151aeca5
📒 Files selected for processing (22)
apps/desktop/src/main/windows/main.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/KeepAliveWorkspaces.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/hooks/usePresetHotkeys.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/ChangesContent.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/FileDiffSection/FileDiffSection.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/PresetsBar/PresetsBar.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/FilesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceIdContext.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceLayout/WorkspaceLayout.tsx
- Remove redundant `genericNavigate` declaration; reuse existing `navigate` from useNavigate() - Gate mouse-nav shim injection to macOS only (PLATFORM.IS_MAC); Windows/Linux already handle side buttons via `app-command`
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
#1 ダッシュボード戻り時のポーリング永続: activeWorkspaceId=null時に deactivateAll()を呼ぶ。setActiveWorkspace(null)対応追加 #2 deactivateされたWSのMapエントリ蓄積: 問題の根本はregisterが増え続ける ことではなく、全WSがisActive:trueで起動していたこと。#3で解消 #3 起動直後の全WSポーリング並走: registerWorkspaceをisActive:falseに変更。 タイマーはactivateWorkspace/setActiveWorkspaceでのみ起動 #4 WS切替時の初回表示5s遅延: activateWorkspace/setActiveWorkspace時に 即時sync(syncPRStatus+syncPRComments)を実行 追加改善: - startTimersに防衛的stopTimers追加(二重タイマー防止) - onWindowFocus()をデッドコードとして削除 - deactivateAll()メソッド追加
…uperset-sh#3517) * remove 7 day rule * Upgrade mastra * upgrade ai * Ad mastra * refactor(desktop): remove dead provider-diagnostics plumbing The provider-diagnostics store was fed by callSmallModel's per-attempt reporting, which was removed when small-model tasks moved to direct AI-SDK + mastracode's AuthStorage. Nothing writes to the issue map anymore, so the clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in ModelsSettings were all no-ops. Settings still surfaces "Session expired / Reconnect" via auth-status alone. ProviderIssue type collapsed from 8 codes to just "expired" to match. * fix(auth): auto-refresh expired Anthropic OAuth tokens Anthropic credentials were read via authStorage.get() everywhere, so mastracode's built-in refresh flow never ran. Once the 1-hour access token expired, status flipped to "Reconnect" and users had to do a full PKCE re-auth, even though a valid refresh token was already stored. Resolvers now call authStorage.getApiKey() for oauth creds on expiry, which triggers refreshToken() and persists the refreshed credential. getAnthropicAuthStatus does the same before declaring issue: "expired". Mirrors the pattern already used for OpenAI small-model auth. * review: address PR feedback from cubic + coderabbit + greptile - host-service ai-branch-name: run trailing-trim after slice so a 100-char truncation can't re-introduce a bare "." or "-" that git rejects as an invalid ref (coderabbit / cubic #2, #7). - host-service workspace-creation.generateBranchName: reuse the existing listBranchNames helper instead of the inline git walk, which classified off the short refname and could conflate a local "origin/foo" with refs/remotes/origin/foo (coderabbit #3). - packages/chat shared/small-model: drop the unused hasSmallModelCredentials export; only a test mock consumed it (greptile #4). - resolveAnthropicCredential: on refresh failure, return null instead of kind:"oauth" with a stale expiresAt so callers fall back cleanly (cubic #8). - chat-service.getAnthropicAuthStatus: log context when refresh throws instead of silently swallowing (cubic #9). * fix(chat): read auth.json directly instead of importing mastracode Importing createAuthStorage from mastracode loads the entire CLI tree (fastembed → onnxruntime-node's 208 MB native binary) via eager top-level requires in mastracode's CJS entry. This crashed electron-vite bundling and bloated the get-small-model chunk. getSmallModel now reads mastracode's auth.json file directly using the same path resolution logic (~/Library/Application Support/mastracode/ on macOS). Zero mastracode import, zero bundle impact. The chunk stays at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai). Production build verified: compile:app succeeds, Electron main process boots with no onnxruntime error. * docs(desktop): add manual testing plan for PR superset-sh#3517 * fix api key storage slot * fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now use authStorage.setStoredApiKey() (writes to "apikey:<provider>") instead of authStorage.set() (writes to the main "<provider>" slot shared with OAuth). This way connecting/disconnecting OAuth doesn't overwrite or delete a stored API key. resolveAuthMethodForProvider falls back to hasStoredApiKey() after checking the main slot, so status correctly reports authenticated when only an API key is stored. * fix(auth): backup/restore API keys across OAuth connect/disconnect mastracode's resolveModel only reads API keys from the main authStorage slot (authStorage.get("anthropic")). OAuth login overwrites this slot, and disconnect removes it — losing any previously saved API key. Fix: backup the API key to the dedicated apikey: slot before OAuth connect, restore it after disconnect. setApiKeyForProvider now writes to both slots (main for resolveModel compatibility, apikey: for backup). resolveAuthMethodForProvider checks both. Applies to both Anthropic and OpenAI providers. * chore: add upstream PR reference to auth workaround Point to mastra-ai/mastra#15483 so the backup/restore code can be removed once upstream lands and we bump mastracode. * refactor(desktop): derive settings provider action from status Replace the cascade of if/else + canDisconnect flag with a single getProviderAction(status) → connect | reconnect | logout | null. Fixes "Active" badge + "Connect" button showing simultaneously when authenticated via API key. * fix(desktop): always show Logout when provider is active Active providers now always show a Logout button. Clears OAuth or API key depending on authMethod — no more "Active" badge with no way to disconnect. * fix(desktop): simplify OpenAI OAuth dialog + auto-open browser Match Anthropic dialog's layout: remove the raw OAuth URL display and "Tip" block, auto-open the browser on OAuth start. Change "Back" to "Cancel" for consistency. * refactor(desktop): unify OAuth dialogs into shared OAuthDialog Extract shared OAuthDialog component with provider config object. AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers that pass provider-specific labels and options. * fix(desktop): show 'Copied!' feedback on Copy URL button * refactor(desktop): merge provider account + API key into single card Each provider section now renders AccountCard + ConfigRow inside one rounded card with a divider, instead of two separate cards. Removes the standalone "API Keys" collapsible section. * refactor(desktop): compact OAuth row in provider settings card OAuth row is now a single inline row (label + status + action) instead of a stacked AccountCard. Both providers share the same 2-row card layout: OAuth row + API key row with divider. * fix(desktop): contextual buttons in provider settings Connect is now primary (filled). Save only shows when there's input. Clear only shows when a key is saved. Removes visual noise from empty-state provider cards. * ui(desktop): add provider icons to settings section headers * ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers * ui: remove redundant disconnected subtitle * ui: remove subtitle text from OAuth rows * chore: remove dead AccountCard + getProviderSubtitle * docs: update test plan to match current UI * chore: move shipped plans to done/ --------- Co-authored-by: AviPeltz <aj.peltz@gmail.com>
…uperset-sh#3517) * remove 7 day rule * Upgrade mastra * upgrade ai * Ad mastra * refactor(desktop): remove dead provider-diagnostics plumbing The provider-diagnostics store was fed by callSmallModel's per-attempt reporting, which was removed when small-model tasks moved to direct AI-SDK + mastracode's AuthStorage. Nothing writes to the issue map anymore, so the clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in ModelsSettings were all no-ops. Settings still surfaces "Session expired / Reconnect" via auth-status alone. ProviderIssue type collapsed from 8 codes to just "expired" to match. * fix(auth): auto-refresh expired Anthropic OAuth tokens Anthropic credentials were read via authStorage.get() everywhere, so mastracode's built-in refresh flow never ran. Once the 1-hour access token expired, status flipped to "Reconnect" and users had to do a full PKCE re-auth, even though a valid refresh token was already stored. Resolvers now call authStorage.getApiKey() for oauth creds on expiry, which triggers refreshToken() and persists the refreshed credential. getAnthropicAuthStatus does the same before declaring issue: "expired". Mirrors the pattern already used for OpenAI small-model auth. * review: address PR feedback from cubic + coderabbit + greptile - host-service ai-branch-name: run trailing-trim after slice so a 100-char truncation can't re-introduce a bare "." or "-" that git rejects as an invalid ref (coderabbit / cubic #2, #7). - host-service workspace-creation.generateBranchName: reuse the existing listBranchNames helper instead of the inline git walk, which classified off the short refname and could conflate a local "origin/foo" with refs/remotes/origin/foo (coderabbit #3). - packages/chat shared/small-model: drop the unused hasSmallModelCredentials export; only a test mock consumed it (greptile #4). - resolveAnthropicCredential: on refresh failure, return null instead of kind:"oauth" with a stale expiresAt so callers fall back cleanly (cubic #8). - chat-service.getAnthropicAuthStatus: log context when refresh throws instead of silently swallowing (cubic #9). * fix(chat): read auth.json directly instead of importing mastracode Importing createAuthStorage from mastracode loads the entire CLI tree (fastembed → onnxruntime-node's 208 MB native binary) via eager top-level requires in mastracode's CJS entry. This crashed electron-vite bundling and bloated the get-small-model chunk. getSmallModel now reads mastracode's auth.json file directly using the same path resolution logic (~/Library/Application Support/mastracode/ on macOS). Zero mastracode import, zero bundle impact. The chunk stays at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai). Production build verified: compile:app succeeds, Electron main process boots with no onnxruntime error. * docs(desktop): add manual testing plan for PR superset-sh#3517 * fix api key storage slot * fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now use authStorage.setStoredApiKey() (writes to "apikey:<provider>") instead of authStorage.set() (writes to the main "<provider>" slot shared with OAuth). This way connecting/disconnecting OAuth doesn't overwrite or delete a stored API key. resolveAuthMethodForProvider falls back to hasStoredApiKey() after checking the main slot, so status correctly reports authenticated when only an API key is stored. * fix(auth): backup/restore API keys across OAuth connect/disconnect mastracode's resolveModel only reads API keys from the main authStorage slot (authStorage.get("anthropic")). OAuth login overwrites this slot, and disconnect removes it — losing any previously saved API key. Fix: backup the API key to the dedicated apikey: slot before OAuth connect, restore it after disconnect. setApiKeyForProvider now writes to both slots (main for resolveModel compatibility, apikey: for backup). resolveAuthMethodForProvider checks both. Applies to both Anthropic and OpenAI providers. * chore: add upstream PR reference to auth workaround Point to mastra-ai/mastra#15483 so the backup/restore code can be removed once upstream lands and we bump mastracode. * refactor(desktop): derive settings provider action from status Replace the cascade of if/else + canDisconnect flag with a single getProviderAction(status) → connect | reconnect | logout | null. Fixes "Active" badge + "Connect" button showing simultaneously when authenticated via API key. * fix(desktop): always show Logout when provider is active Active providers now always show a Logout button. Clears OAuth or API key depending on authMethod — no more "Active" badge with no way to disconnect. * fix(desktop): simplify OpenAI OAuth dialog + auto-open browser Match Anthropic dialog's layout: remove the raw OAuth URL display and "Tip" block, auto-open the browser on OAuth start. Change "Back" to "Cancel" for consistency. * refactor(desktop): unify OAuth dialogs into shared OAuthDialog Extract shared OAuthDialog component with provider config object. AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers that pass provider-specific labels and options. * fix(desktop): show 'Copied!' feedback on Copy URL button * refactor(desktop): merge provider account + API key into single card Each provider section now renders AccountCard + ConfigRow inside one rounded card with a divider, instead of two separate cards. Removes the standalone "API Keys" collapsible section. * refactor(desktop): compact OAuth row in provider settings card OAuth row is now a single inline row (label + status + action) instead of a stacked AccountCard. Both providers share the same 2-row card layout: OAuth row + API key row with divider. * fix(desktop): contextual buttons in provider settings Connect is now primary (filled). Save only shows when there's input. Clear only shows when a key is saved. Removes visual noise from empty-state provider cards. * ui(desktop): add provider icons to settings section headers * ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers * ui: remove redundant disconnected subtitle * ui: remove subtitle text from OAuth rows * chore: remove dead AccountCard + getProviderSubtitle * docs: update test plan to match current UI * chore: move shipped plans to done/ --------- Co-authored-by: AviPeltz <aj.peltz@gmail.com>
…sions #1 Target.closeTarget UI integrity v1/v2 secondary tab registry が webview "close" イベントを購読。 MCP の Target.closeTarget で Chromium が webContents を破棄すると guest 側が close を発火し、registry が closeTab → unregisterTab 経由で paneTabTargetIds を整理。tab バー UI と CDP allowedTargetIds が同期した状態を保つ。 #2 target="_blank" / window.open を MCP 可視に windowOpenHandler の非 new-window 分岐で new-window event を emit していた箇所を create-tab-requested:${paneId} に置換。同じペイン 内の secondary tab として生成されるので paneTabTargetIds に入り、 MCP が list_pages / select_page で扱える。Chrome の target="_blank" デフォルト (新タブ) 挙動に揃う。split-pane / workspace-tab が 欲しいケースは既存の "Open in Split" コンテキストメニューでカバー。 #3 非 media 権限の UI prompt 化 SITE_PERMISSION_KINDS に geolocation / notifications / clipboard-read を追加。browser-site-permission-manager が Electron の setPermissionRequestHandler で media 同様の consent flow に乗せる。 既存の permissionRequested イベント経路はそのまま再利用。 認識しない permission は従来通り permissive で許可。
upstream 取り込み PR #2: v2 UI 小粒 / marketing / 9 commits
概要
Electronの
<webview>タグはDOMの親要素が変わる(reparent)と、コンテンツが完全にリロードされる。従来はタブ/ワークスペース切り替え時にBrowserPaneがアンマウントされ、webviewが隠しコンテナに移動(reparent)されていたため、毎回ハードリロードが発生していた。このPRでは、webviewを含むタブとワークスペースページをマウントしたまま維持し、DOMのreparentを完全に排除することでリロードを防止する。
また、webview内でマウスの戻る/進むボタンが効かない問題も修正。
変更内容
タブ切り替えのリロード防止(PersistentTabRenderer)
ワークスペース切り替えのリロード防止(KeepAliveWorkspaces)
DashboardLayoutレベル(ルーターのOutletの上)で保持WorkspacePageがworkspaceIdOverrideプロップを受け取り、ルーターのマッチ外でも動作可能にworkspaceIdをuseParams()ではなくprops経由でWorkspaceLayout → ContentView → TabsContentに伝搬。非アクティブなワークスペースが誤ったIDを読む問題を防止コンテキスト分離(WorkspaceIdContext)
useParams()でworkspaceIdを取得していた9コンポーネントを、WorkspacePageが提供するReact Contextから読むように変更ホットキー分離
WorkspacePage内の全useAppHotkey呼び出しに{ enabled: isActive }を追加usePresetHotkeysも同様にenabledオプションに対応マウスの戻る/進むボタン対応(webview内)
dom-ready時にexecuteJavaScriptでwebviewのゲストページにマウスイベントリスナーを注入BrowserWindowでapp-commandイベントをハンドル永続webviewラッパー
<div>ラッパーで包み、webviewのparentNodeが変わらないようにする備考
このブランチにはリロード修正とは無関係のコミット
a7816ee3(Excel/スプレッドシートファイルビューア)が含まれています。テスト項目
Summary by CodeRabbit
New Features
Improvements