From 66f4ab79321f119e182bfc5e7382f3cf42e2cce2 Mon Sep 17 00:00:00 2001 From: Siddharth Balyan Date: Thu, 8 Oct 2026 21:49:16 +0530 Subject: [PATCH] fix(desktop): a sign-in stays visible when the same profile is picked again Review round 3: on a multi-connection desktop the scope selector pins `{connectionId, profile}` while an operation started from the ambient scope holds the bare profile name, so re-picking the same profile hid the open sign-in. Scopes now compare by the backend they resolve to (connection, else the active one, plus profile). --- .../data/account-operations.test.ts | 6 ++++++ .../connectors/data/account-operations.ts | 20 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/app/capabilities/connectors/data/account-operations.test.ts b/apps/desktop/src/app/capabilities/connectors/data/account-operations.test.ts index 36cae42a14..af2ccbe778 100644 --- a/apps/desktop/src/app/capabilities/connectors/data/account-operations.test.ts +++ b/apps/desktop/src/app/capabilities/connectors/data/account-operations.test.ts @@ -24,4 +24,10 @@ describe('accountOperationFor', () => { expect(accountOperationFor(operations, 'gmail', { connectionId: 'remote', profile: 'home' })?.opId).toBe('b') expect(accountOperationFor(operations, 'gmail', 'other')).toBeNull() }) + + it('treats a bare profile and the same profile pinned to the active connection as one scope', () => { + const operations = { a: operation('a', 'home') } + + expect(accountOperationFor(operations, 'gmail', { connectionId: 'local', profile: 'home' })?.opId).toBe('a') + }) }) diff --git a/apps/desktop/src/app/capabilities/connectors/data/account-operations.ts b/apps/desktop/src/app/capabilities/connectors/data/account-operations.ts index 85b6bf1a90..da09ccfe3d 100644 --- a/apps/desktop/src/app/capabilities/connectors/data/account-operations.ts +++ b/apps/desktop/src/app/capabilities/connectors/data/account-operations.ts @@ -6,8 +6,10 @@ import type { } from '@hermes/shared' import { atom } from 'nanostores' -import { type ProfileScope, profileScopeKey } from '@/hermes' +import type { ProfileScope } from '@/hermes' import { type ConnectionTarget, parseConnectionTarget } from '@/store/connection-request' +import { activeGatewayConnectionId } from '@/store/gateway' +import { normalizeProfileKey } from '@/store/profile' import { invalidateConnectors } from './keys' import { accountOperationStatus } from './rpc' @@ -44,16 +46,28 @@ function resumeConnect(slugs: readonly string[]): void { const parseTargets = (targets: ConnectionUpdatePayload['targets']): ConnectionTarget[] => targets.map(parseConnectionTarget).filter((target): target is ConnectionTarget => target !== null) +// A bare profile name rides the active connection, so `'home'` and `{connectionId: , profile: 'home'}` are +// the same backend and must match. +function resolvedScopeKey(scope: ProfileScope): string { + const active = activeGatewayConnectionId() ?? 'local' + + if (scope instanceof Object) { + return `${(scope.connectionId ?? '').trim() || 'local'}::${normalizeProfileKey(scope.profile)}` + } + + return `${active}::${normalizeProfileKey(scope)}` +} + /** The open (else newest) operation for one app in one profile scope; other profiles' sign-ins are not this one's. */ export function accountOperationFor( operations: Readonly>, slug: string, scope: ProfileScope ): AccountOperation | null { - const key = profileScopeKey(scope) + const key = resolvedScopeKey(scope) const mine = Object.values(operations).filter( - operation => operation.connectors.includes(slug) && profileScopeKey(operation.scope) === key + operation => operation.connectors.includes(slug) && resolvedScopeKey(operation.scope) === key ) return mine.find(operation => !operation.settled) ?? mine[mine.length - 1] ?? null