fix: name the server when two accounts with one address sync to this device

This commit is contained in:
Linus Rath
2026-10-31 20:46:09 +01:00
parent 3c95bba0d2
commit 10f0066ed1
3 changed files with 53 additions and 2 deletions
@@ -15,7 +15,7 @@ import { spacing, typography, type ThemePalette } from '../../../theme/tokens';
import { useColors } from '../../../theme/colors';
import { useLocaleStore, type TranslateFn } from '../../../stores/locale-store';
import { useSettingsStore } from '../../../stores/settings-store';
import type { AccountEntry } from '../../../stores/account-store';
import { useAccountStore, type AccountEntry } from '../../../stores/account-store';
import {
effectiveSelection,
emptyAccountDeviceSync,
@@ -41,6 +41,7 @@ import { describeSyncStatus, type StatusAction } from './status';
import { useAndroidSync } from './use-android-sync';
import { useSyncCollections } from './use-sync-collections';
import { nameWithAccount } from '../../../lib/address-book-label';
import { deviceSyncAccountLabel } from './account-label';
interface Props {
account: AccountEntry;
@@ -117,7 +118,8 @@ export function DeviceSyncAccountRow({ account, authority }: Props) {
[collections, entry, authority],
);
const accountLabel = account.email || account.username;
const accounts = useAccountStore((s) => s.accounts);
const accountLabel = React.useMemo(() => deviceSyncAccountLabel(account, accounts), [account, accounts]);
const status = describeSyncStatus(
{
authority,
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { deviceSyncAccountLabel } from '../account-label';
const account = (id: string, email: string, serverUrl: string) => ({ id, email, username: email, serverUrl });
describe('the account name in Sync to this device', () => {
const a = account('usera@10.10.10.62:8016', 'usera@example.org', 'http://10.10.10.62:8016');
const b = account('usera@10.10.10.62:8120', 'usera@example.org', 'http://10.10.10.62:8120');
const c = account('userb@mail.example.org', 'userb@example.org', 'https://mail.example.org');
it('tells two accounts with the same address apart by their server', () => {
expect(deviceSyncAccountLabel(a, [a, b, c])).toBe('usera@example.org (10.10.10.62:8016)');
expect(deviceSyncAccountLabel(b, [a, b, c])).toBe('usera@example.org (10.10.10.62:8120)');
});
it('is just the address when it is the only one with it', () => {
expect(deviceSyncAccountLabel(c, [a, b, c])).toBe('userb@example.org');
expect(deviceSyncAccountLabel(a, [a, c])).toBe('usera@example.org');
// A default port is left out of the server.
const d = account('usera@mail.example.org', 'USERA@example.org', 'https://mail.example.org');
expect(deviceSyncAccountLabel(d, [a, d])).toBe('USERA@example.org (mail.example.org)');
});
it('is what the row and its switch are labelled with', () => {
const src = fs.readFileSync(path.resolve(__dirname, '..', 'DeviceSyncAccountRow.tsx'), 'utf8');
expect(src).toMatch(/const accountLabel = React\.useMemo\(\(\) => deviceSyncAccountLabel\(account, accounts\), \[account, accounts\]\);/);
expect(src).toMatch(/'Sync \{account\} to this device', \{ account: accountLabel \}/);
});
});
@@ -0,0 +1,18 @@
// The account's name in "Sync to this device": its address, plus the server
// when another signed-in account has the same address (usera@example.org on
// two servers showed two identical "Sync usera@example.org to this device").
import { serverHostLabel } from '../../../lib/account-utils';
interface LabelledAccount {
id: string;
email: string;
username: string;
serverUrl: string;
}
export function deviceSyncAccountLabel(account: LabelledAccount, accounts: readonly LabelledAccount[]): string {
const address = account.email || account.username;
const key = address.trim().toLowerCase();
const twin = accounts.some((a) => a.id !== account.id && (a.email || a.username).trim().toLowerCase() === key);
return twin ? `${address} (${serverHostLabel(account.serverUrl)})` : address;
}