Skip to content

feat(desktop): ブラウザタブのズーム機能#9

Closed
MocA-Love wants to merge 4 commits intomainfrom
feat/desktop-browser-zoom
Closed

feat(desktop): ブラウザタブのズーム機能#9
MocA-Love wants to merge 4 commits intomainfrom
feat/desktop-browser-zoom

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Mar 28, 2026

概要

ブラウザタブのツールバーにズーム機能(拡大・縮小・リセット)を追加しました。

変更内容

  • usePersistentWebview.ts: zoomIn, zoomOut, resetZoom, zoomLevel を追加。webview の setZoomLevel() / getZoomLevel() API を直接操作
  • BrowserPane.tsx: DevTools ボタンの左側に [-] [100%] [+] ボタンを配置

仕様

項目
デフォルト 100% (zoomLevel = 0)
最小 約 50% (zoomLevel = -3)
最大 約 200% (zoomLevel = 3)
増減単位 zoomLevel ±0.5
リセット パーセント表示をクリック

UI 配置

[URL入力] | [-] [100%] [+] [DevTools] [⋯] [Split] [Close]

コンフリクトリスク

バックエンドや型定義は変更なし。レンダラー側の2ファイルのみ(うち1ファイルはフォーク変更済み)。

テスト計画

  • [-] ボタンでページが縮小されること
  • [+] ボタンでページが拡大されること
  • パーセント表示が正しく更新されること
  • パーセント表示クリックで 100% にリセットされること
  • タブ切り替え後もズーム状態が維持されること

Summary by CodeRabbit

  • New Features
    • Added zoom controls to the browser tab toolbar, enabling users to zoom in/out (50%–200% range) and reset to 100% by clicking the percentage display.

webview の zoom API を直接操作し、ツールバーに [-] [%] [+] ボタンを配置。
50%〜200% の範囲でズーム可能、パーセント表示クリックで 100% にリセット。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

Warning

Rate limit exceeded

@MocA-Love has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 26 seconds before requesting another review.

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 10 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 907aa533-677f-42b3-ab68-284435fc6a3a

📥 Commits

Reviewing files that changed from the base of the PR and between 549ebf4 and 52d4227.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
📝 Walkthrough

Walkthrough

A new browser tab zoom feature is introduced with UI controls in the BrowserPane component and corresponding tRPC backend procedures to manage zoom levels across a 50%–200% range with 100% reset capability. Documentation is updated accordingly.

Changes

Cohort / File(s) Summary
Documentation
README.md
Adds entry describing the new "ブラウザタブのズーム機能" (browser tab zoom feature) with details on zoom controls and supported range.
Zoom Controls
apps/desktop/src/renderer/.../BrowserPane/BrowserPane.tsx
Introduces local zoomLevel state, zoom buttons (in/out/reset) in toolbar, and integrates tRPC mutation setZoomLevel with clamping logic between ZOOM_MIN and ZOOM_MAX.
Browser API
apps/desktop/src/lib/trpc/routers/browser/browser.ts
Adds setZoomLevel mutation and getZoomLevel query procedures to interface with Electron WebContents zoom API.

Sequence Diagram

sequenceDiagram
    actor User
    participant BrowserPane as BrowserPane<br/>Component
    participant tRPC as tRPC<br/>Client
    participant Server as Browser<br/>Router
    participant WebContents as Electron<br/>WebContents

    User->>BrowserPane: Click zoom button
    BrowserPane->>BrowserPane: Calculate new zoom level<br/>(clamp to min/max)
    BrowserPane->>tRPC: applyZoom(level)
    tRPC->>Server: electronTrpc.browser.setZoomLevel
    Server->>WebContents: setZoomLevel(level)
    WebContents-->>Server: Zoom applied
    Server-->>tRPC: { success: true, level }
    tRPC-->>BrowserPane: Response received
    BrowserPane->>BrowserPane: Update zoomLevel state
    BrowserPane-->>User: Display updated zoom %
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A zoom button bounces, left and right,
Magnifying tabs with digital might,
Fifty to two-hundred, in cheerful display,
One hundred's reset—a hop, skip, hooray! 🔍✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is well-structured with overview, changes, specifications, UI layout, conflict risk assessment, and test plan. However, it does not follow the provided template which requires sections like 'Description', 'Related Issues', 'Type of Change', 'Testing', 'Screenshots', and 'Additional Notes'. Reorganize the description to match the provided template structure: add 'Related Issues' section, explicitly mark 'Type of Change' as 'New feature', and reformat test items as a proper 'Testing' section.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding zoom functionality to browser tabs. It is concise, specific, and directly reflects the primary feature being introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-browser-zoom

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx (1)

105-111: Add aria-label to icon-only zoom buttons.

The minus/plus controls are icon-only; adding labels improves screen-reader usability without changing behavior.

♿ Proposed accessibility tweak
 												<button
 													type="button"
 													onClick={zoomOut}
+													aria-label="Zoom out"
 													className="rounded p-0.5 text-muted-foreground/60 transition-colors hover:text-muted-foreground"
 												>
@@
 												<button
 													type="button"
 													onClick={zoomIn}
+													aria-label="Zoom in"
 													className="rounded p-0.5 text-muted-foreground/60 transition-colors hover:text-muted-foreground"
 												>

Also applies to: 133-139

🤖 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/BrowserPane/BrowserPane.tsx`
around lines 105 - 111, Add accessible labels to the icon-only zoom buttons in
BrowserPane by adding aria-label attributes to the button elements that call
zoomOut and zoomIn (the button with onClick={zoomOut} and the corresponding
button with onClick={zoomIn}); set descriptive values like "Zoom out" and "Zoom
in" respectively so screen readers convey the control purpose without changing
visual behavior or handlers.
🤖 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/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts`:
- Around line 468-491: The zoom state is currently hardcoded to 0 which causes
the UI to show 100% after remount even if a persisted webview has a different
zoom; change initialization to read the current zoom from the persisted webview
in webviewRegistry using paneId (or run a sync useEffect on mount) and call
setZoomLevelState with webview.getZoomLevel() when a webview exists (e.g.,
replace useState(0) with an initializer that returns
webviewRegistry.get(paneId)?.getZoomLevel() ?? 0 or add a one-time effect to
setZoomLevelState(webview.getZoomLevel())); keep zoomIn/zoomOut/reset logic
as-is but ensure initial sync uses the same webview.getZoomLevel() value.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx`:
- Around line 105-111: Add accessible labels to the icon-only zoom buttons in
BrowserPane by adding aria-label attributes to the button elements that call
zoomOut and zoomIn (the button with onClick={zoomOut} and the corresponding
button with onClick={zoomIn}); set descriptive values like "Zoom out" and "Zoom
in" respectively so screen readers convey the control purpose without changing
visual behavior or handlers.
🪄 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: d2774ad1-77a2-49c9-a726-3738132d7450

📥 Commits

Reviewing files that changed from the base of the PR and between e0bce47 and 0837168.

📒 Files selected for processing (3)
  • README.md
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts

Comment on lines +468 to +491
const [zoomLevel, setZoomLevelState] = useState(0);

const zoomIn = useCallback(() => {
const webview = webviewRegistry.get(paneId);
if (!webview) return;
const next = Math.min(ZOOM_MAX, webview.getZoomLevel() + ZOOM_STEP);
webview.setZoomLevel(next);
setZoomLevelState(next);
}, [paneId]);

const zoomOut = useCallback(() => {
const webview = webviewRegistry.get(paneId);
if (!webview) return;
const next = Math.max(ZOOM_MIN, webview.getZoomLevel() - ZOOM_STEP);
webview.setZoomLevel(next);
setZoomLevelState(next);
}, [paneId]);

const resetZoom = useCallback(() => {
const webview = webviewRegistry.get(paneId);
if (!webview) return;
webview.setZoomLevel(0);
setZoomLevelState(0);
}, [paneId]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Initialize zoom state from the persistent webview instead of hardcoding 0.

Line 468 resets UI state to 100% on remount even when the persisted webview is still zoomed, so the displayed percentage can drift from actual zoom after pane/tab lifecycle changes.

💡 Proposed fix
-	const [zoomLevel, setZoomLevelState] = useState(0);
+	const [zoomLevel, setZoomLevelState] = useState(() => {
+		const webview = webviewRegistry.get(paneId);
+		return webview ? webview.getZoomLevel() : 0;
+	});
🤖 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/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts`
around lines 468 - 491, The zoom state is currently hardcoded to 0 which causes
the UI to show 100% after remount even if a persisted webview has a different
zoom; change initialization to read the current zoom from the persisted webview
in webviewRegistry using paneId (or run a sync useEffect on mount) and call
setZoomLevelState with webview.getZoomLevel() when a webview exists (e.g.,
replace useState(0) with an initializer that returns
webviewRegistry.get(paneId)?.getZoomLevel() ?? 0 or add a one-time effect to
setZoomLevelState(webview.getZoomLevel())); keep zoomIn/zoomOut/reset logic
as-is but ensure initial sync uses the same webview.getZoomLevel() value.

webview タグの setZoomLevel() がホストページに効いてしまう問題を修正。
tRPC 経由でメインプロセスの webContents.setZoomLevel() を呼ぶ方式に変更。
Electron の webContents.setZoomLevel() がホストページにも影響する問題を修正。
executeJavaScript で guest ページの document.documentElement.style.zoom を
直接設定する方式に変更。tRPC のズームプロシージャは不要となったため削除。
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/browser/browser.ts (1)

153-160: Consider adding bounds validation for level as a defensive measure.

The PR specifies a zoom level range of -3 to 3. While the UI enforces these limits, adding server-side validation would prevent unexpected behavior if called with out-of-range values.

🛡️ Optional: Add bounds validation
 		setZoomLevel: publicProcedure
-			.input(z.object({ paneId: z.string(), level: z.number() }))
+			.input(z.object({ paneId: z.string(), level: z.number().min(-3).max(3) }))
 			.mutation(({ input }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/browser/browser.ts` around lines 153 - 160,
Validate the zoom `level` input in the setZoomLevel publicProcedure (the
z.object({ paneId, level }) handler) to enforce the -3..3 bounds before calling
browserManager.getWebContents and wc.setZoomLevel; if level is out of range,
either return { success: false, level: currentOr0 } or clamp to the nearest
bound and proceed—update the mutation to check input.level against -3 and 3,
handle the invalid case consistently (return failure or clamp) and ensure the
response reflects the applied level.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/browser/browser.ts`:
- Around line 153-160: Validate the zoom `level` input in the setZoomLevel
publicProcedure (the z.object({ paneId, level }) handler) to enforce the -3..3
bounds before calling browserManager.getWebContents and wc.setZoomLevel; if
level is out of range, either return { success: false, level: currentOr0 } or
clamp to the nearest bound and proceed—update the mutation to check input.level
against -3 and 3, handle the invalid case consistently (return failure or clamp)
and ensure the response reflects the applied level.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d458f895-b5c1-46a2-a4bc-dd7ffa1fd072

📥 Commits

Reviewing files that changed from the base of the PR and between 0837168 and 549ebf4.

📒 Files selected for processing (2)
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx

CSS zoom はページ読み込み時にリセットされるため、zoomFactorRegistry に
現在のズーム値を保持し、did-stop-loading 時に再注入するように修正。
@MocA-Love MocA-Love closed this Mar 28, 2026
@MocA-Love MocA-Love deleted the feat/desktop-browser-zoom branch March 29, 2026 05:05
MocA-Love pushed a commit that referenced this pull request Apr 18, 2026
…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>
MocA-Love pushed a commit that referenced this pull request Apr 18, 2026
…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>
MocA-Love added a commit that referenced this pull request Apr 24, 2026
upstream 取り込み PR #9: 計画後前進分 12 commits + bun.lock regen
MocA-Love added a commit that referenced this pull request Apr 24, 2026
… upstream superset-sh#3697)

upstream superset-sh#3697 の split 構造 (24 ファイル) を fork に取り込み、fork
独自拡張を新 split 構造に再配置。workspace-creation.ts を 1758 行から
28 行の facade に縮小。

- schemas.ts: 全 procedure の zod schemas (baseBranchSource,
  workspaceNameWasAutoGenerated, checkoutPrSchema XOR refine を保持)
- shared/: 11 ファイル (types, project-helpers, local-project,
  progress-store, git-config, setup-terminal, worktree-paths,
  start-point, branch-search, finish-checkout, types)
- procedures/: 11 ファイル (adopt, checkout, create, generate-branch-name,
  get-context, get-github-issue-content, get-github-pull-request-content,
  get-progress, search-branches, search-github-issues, search-pull-requests)
- utils/: 追加 (ai-workspace-names.ts, list-branch-names.ts)

1. **baseBranchSource** (picker hint: local/remote-tracking) →
   schemas.ts + procedures/create.ts + procedures/adopt.ts
2. **PR checkout mode** (derivePrLocalBranchName, detached worktree,
   gh pr checkout --force, pre-existing branch/closed/merged warnings) →
   procedures/checkout.ts
3. **FORK NOTE** コメント群 → 関連 procedure に復元
4. **GitHub link command wiring** (gh issue develop, gh pr create) →
   各 procedure
5. **normalize-github-query.ts** (fork-only URL normalization) → 維持
6. **applyAiWorkspaceRename** + updateNameFromHost no-op detection →
   utils/ai-workspace-names.ts で復元 (PR #9 で fork main から欠落して
   いた関数を本 PR で供給)
7. **worktree path 方針**: <repoPath>/.worktrees/<branch> を維持
   (upstream の ~/.superset/worktrees/<projectId>/<branch> は採用しない。
   既存 workspace/file watcher/migration 互換性のため)

packages/trpc/src/router/v2-workspace/v2-workspace.ts に
`updateNameFromHost` JWT procedure を追加。expectedCurrentName WHERE
guard で並行 user rename の race を検出し、no-op 時は既存 row を
返して host-service 側で git branch rollback できるようにする。

- ai-workspace-names.ts: getSmallModel() が unknown を返すため、
  Agent の model parameter に biome-ignore 付きで cast
- v2-workspace.updateNameFromHost: findFirst の columns に branch
  を追加 (no-op 時の caller での branch 比較に必要)

- bun run typecheck: 27/27 successful
- bun run lint: pass (rg 未 install の環境警告除く)
- fork 固有機能: 19 procedure, ansi_up/@vscode/ripgrep/@xyflow/react,
  TERMINAL_OPTIONS, SUPERSET_WORKSPACE_NAME, dmg.size=4g, desktop 1.5.10
  全健在

packages/host-service/package.json に @mastra/core: 1.25.0 を追加
(applyAiWorkspaceRename が Agent を使うため)。packages/chat が既に
同 version を持つため workspace 共有で解決される。

Closes #415
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