From 33ed7d33fe710709490e9ea3fdd7fe4162b5cebd Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sat, 14 Mar 2026 09:25:35 +0100 Subject: [PATCH] fix(ui): deduplicate usage profiles and make dropdowns scrollable - Deduplicate ProfileUsageSummary entries by email in usage-monitor.ts so multiple ClaudeProfileManager profiles sharing the same underlying OAuth account (different configDir) collapse to one entry - Add max-h-[80vh] overflow-y-auto to all UsageIndicator PopoverContent instances so the dropdown scrolls with many accounts - Add max-h-[60vh] overflow-y-auto to GitHubSetupModal "Connect to AI" provider list so Continue/Skip buttons stay visible - Restructure onboarding AccountsStep with pinned header/footer and independently scrollable provider list Co-Authored-By: Claude Opus 4.6 --- .../src/main/claude-profile/usage-monitor.ts | 41 +++++++++++++++++-- .../renderer/components/GitHubSetupModal.tsx | 4 +- .../renderer/components/UsageIndicator.tsx | 8 ++-- .../components/onboarding/AccountsStep.tsx | 28 +++++++------ 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/apps/desktop/src/main/claude-profile/usage-monitor.ts b/apps/desktop/src/main/claude-profile/usage-monitor.ts index f1af7511..42bfbcf1 100644 --- a/apps/desktop/src/main/claude-profile/usage-monitor.ts +++ b/apps/desktop/src/main/claude-profile/usage-monitor.ts @@ -442,6 +442,9 @@ export class UsageMonitor extends EventEmitter { // Include Z.AI provider accounts from providerAccounts await this.appendZAIAccounts(allProfiles); + // Deduplicate by email (multiple profiles can share the same underlying account) + const deduped = this.deduplicateProfilesByEmail(allProfiles); + // Return minimal data with auth status - don't return null! return { activeProfile: { @@ -452,7 +455,7 @@ export class UsageMonitor extends EventEmitter { fetchedAt: new Date(), needsReauthentication: this.needsReauthProfiles.has(activeProfileId || '') }, - allProfiles, + allProfiles: deduped, fetchedAt: new Date() }; } @@ -752,17 +755,49 @@ export class UsageMonitor extends EventEmitter { // Include Z.AI provider accounts from providerAccounts await this.appendZAIAccounts(allProfiles); + // Deduplicate by email (multiple profiles can share the same underlying account) + const deduplicatedProfiles = this.deduplicateProfilesByEmail(allProfiles); + // Sort by availability score (highest first = most available) - allProfiles.sort((a, b) => b.availabilityScore - a.availabilityScore); + deduplicatedProfiles.sort((a, b) => b.availabilityScore - a.availabilityScore); return { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion activeProfile: this.currentUsage!, // Non-null: _doGetAllProfilesUsage is only called when currentUsage is set - allProfiles, + allProfiles: deduplicatedProfiles, fetchedAt: new Date() }; } + /** + * Deduplicate profiles by email. Multiple ClaudeProfileManager profiles can share + * the same email (different configDir pointing to the same underlying OAuth account). + * Keeps one entry per email, preferring the one with higher usage or active status. + */ + private deduplicateProfilesByEmail(profiles: ProfileUsageSummary[]): ProfileUsageSummary[] { + const result: ProfileUsageSummary[] = []; + const seenEmails = new Map(); // email -> index in result + for (const profile of profiles) { + const key = profile.profileEmail?.toLowerCase(); + if (key && seenEmails.has(key)) { + const existingIdx = seenEmails.get(key)!; + const existing = result[existingIdx]; + const existingMax = Math.max(existing.sessionPercent, existing.weeklyPercent); + const currentMax = Math.max(profile.sessionPercent, profile.weeklyPercent); + if (currentMax > existingMax || (currentMax === existingMax && profile.isActive)) { + result[existingIdx] = profile; + } + } else { + seenEmails.set(key ?? profile.profileId, result.length); + result.push(profile); + } + } + if (result.length < profiles.length) { + console.log('[UsageMonitor] Deduplicated profiles by email:', profiles.length, '→', result.length); + } + return result; + } + /** * Fetch usage for an inactive profile using its own credentials * This allows showing real usage data for non-active profiles diff --git a/apps/desktop/src/renderer/components/GitHubSetupModal.tsx b/apps/desktop/src/renderer/components/GitHubSetupModal.tsx index a0a6ae96..98207e2c 100644 --- a/apps/desktop/src/renderer/components/GitHubSetupModal.tsx +++ b/apps/desktop/src/renderer/components/GitHubSetupModal.tsx @@ -400,11 +400,11 @@ export function GitHubSetupModal({ -
+
{getProviderAccounts().length > 0 && ( -
+

{t('githubSetup.aiProviderReady')} diff --git a/apps/desktop/src/renderer/components/UsageIndicator.tsx b/apps/desktop/src/renderer/components/UsageIndicator.tsx index f7270260..7d9f5df2 100644 --- a/apps/desktop/src/renderer/components/UsageIndicator.tsx +++ b/apps/desktop/src/renderer/components/UsageIndicator.tsx @@ -828,7 +828,7 @@ export function UsageIndicator() { @@ -959,7 +959,7 @@ export function UsageIndicator() { @@ -1099,7 +1099,7 @@ export function UsageIndicator() { @@ -1317,7 +1317,7 @@ export function UsageIndicator() { diff --git a/apps/desktop/src/renderer/components/onboarding/AccountsStep.tsx b/apps/desktop/src/renderer/components/onboarding/AccountsStep.tsx index c8410cd6..66b4cc7a 100644 --- a/apps/desktop/src/renderer/components/onboarding/AccountsStep.tsx +++ b/apps/desktop/src/renderer/components/onboarding/AccountsStep.tsx @@ -15,35 +15,39 @@ interface AccountsStepProps { * Replaces the old AuthChoiceStep + OAuthStep two-step flow with a single * step that reuses the ProviderAccountsList from settings. Users can add * accounts from any supported provider (Anthropic, OpenAI, Google, etc.). + * + * Layout: The header and action buttons are pinned (always visible), while + * the provider list scrolls independently. This prevents the "Continue" + * button from being hidden below the fold on smaller screens. */ export function AccountsStep({ onNext, onBack, onSkip }: AccountsStepProps) { const { t } = useTranslation('onboarding'); return (

-
- {/* Header */} -
-
-
- +
+ {/* Header — pinned at top */} +
+
+
+
-

+

{t('accounts.title')}

-

+

{t('accounts.description')}

- {/* Provider accounts list - reused from settings */} -
+ {/* Provider accounts list — scrollable */} +
- {/* Action Buttons */} -
+ {/* Action Buttons — pinned at bottom, always visible */} +