From 668e53dff620d90f04c0547c7803ac705463b42f Mon Sep 17 00:00:00 2001 From: NessZerra <90105158+Finesssee@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:45:46 +0700 Subject: [PATCH 1/2] Redact Codex System Account labels --- .../src-tauri/src/tray_accounts.rs | 42 +++++++++------ .../src/components/CodexAccountsMenu.test.tsx | 31 +++++++---- .../src/components/CodexAccountsMenu.tsx | 45 +++------------- .../components/codexAccountDisplay.test.ts | 41 +++++++++++++- .../src/components/codexAccountDisplay.ts | 32 +++++++++++ .../settings/providers/ProviderDetailPane.tsx | 6 ++- .../credentials/CodexAccountsSection.test.tsx | 53 +++++++++++++++++++ .../credentials/CodexAccountsSection.tsx | 12 +++-- .../surfaces/settings/tabs/ProvidersTab.tsx | 1 + rust/src/codex_accounts/models.rs | 46 ++++++++++++++++ 10 files changed, 242 insertions(+), 67 deletions(-) diff --git a/apps/desktop-tauri/src-tauri/src/tray_accounts.rs b/apps/desktop-tauri/src-tauri/src/tray_accounts.rs index 6c280d7587..aeb51580cf 100644 --- a/apps/desktop-tauri/src-tauri/src/tray_accounts.rs +++ b/apps/desktop-tauri/src-tauri/src/tray_accounts.rs @@ -252,10 +252,11 @@ fn codex_account_menu_label( hide_personal_info: bool, ordinal: usize, ) -> String { - if hide_personal_info { - return format!("{} {ordinal}", locale::get_text(lang, LocaleKey::Account)); - } - account.display_name() + account.privacy_safe_display_name( + hide_personal_info, + ordinal, + &locale::get_text(lang, LocaleKey::Account), + ) } fn claude_accounts_menu( @@ -444,6 +445,7 @@ mod tests { let empty = codex_accounts_menu(&[], None, Language::English, false); assert!(menu_contains(&empty.children, "add_codex_account")); let mut email_account = second; + email_account.source = CodexAccountSource::Ambient; email_account.nickname = None; email_account.email_hint = Some("private@example.com".into()); let private = codex_accounts_menu(&[email_account.clone()], None, Language::English, true); @@ -470,16 +472,18 @@ mod tests { None, ) }; - let with_nickname = make( + let mut with_nickname = make( "00000000-0000-0000-0000-000000000002", Some("Work"), "user@example.com", ); - let without_nickname = make( + let mut without_nickname = make( "00000000-0000-0000-0000-000000000001", None, "personal@example.com", ); + with_nickname.source = CodexAccountSource::ManagedByApp; + without_nickname.source = CodexAccountSource::Ambient; let accounts = [with_nickname.clone(), without_nickname.clone()]; let ordinals = ordinals_by_id(&accounts); @@ -492,7 +496,7 @@ mod tests { true, ordinals[&with_nickname.id], ), - "Account 2" + "user@example.com — Work" ); assert_eq!( codex_account_menu_label( @@ -504,14 +508,22 @@ mod tests { "Account 1" ); - let hidden = codex_accounts_menu(&accounts, None, Language::English, true); - assert_eq!(hidden.children[0].label, "Account 2"); + let hidden = + codex_accounts_menu(&accounts, Some(&without_nickname), Language::English, true); + assert_eq!(hidden.children[0].label, "user@example.com — Work"); assert_eq!(hidden.children[1].label, "Account 1"); - for entry in hidden.children.iter().take(2) { - assert!(!entry.label.contains('@')); - assert!(!entry.label.contains("example.com")); - assert!(!entry.label.contains("Work")); - } + assert_eq!(hidden.children[0].checked, Some(false)); + assert!(!hidden.children[0].disabled); + assert_eq!(hidden.children[1].checked, Some(true)); + assert!(hidden.children[1].disabled); + assert_eq!( + hidden.children[1].id.as_deref(), + Some(format!("switch_codex_account:{}", without_nickname.id).as_str()) + ); + assert!(hidden.children[0].label.contains("Work")); + assert!(!hidden.children[1].label.contains('@')); + assert!(!hidden.children[1].label.contains("example.com")); + assert!(!hidden.children[1].label.contains("personal")); let reversed = codex_accounts_menu( &[without_nickname.clone(), with_nickname.clone()], @@ -520,7 +532,7 @@ mod tests { true, ); assert_eq!(reversed.children[0].label, "Account 1"); - assert_eq!(reversed.children[1].label, "Account 2"); + assert_eq!(reversed.children[1].label, "user@example.com — Work"); let visible = codex_accounts_menu(&[with_nickname], None, Language::English, false); assert_eq!(visible.children[0].label, "user@example.com — Work"); diff --git a/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx b/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx index 737613b299..2cd1c79c0a 100644 --- a/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx +++ b/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx @@ -202,7 +202,7 @@ describe("CodexAccountsMenu", () => { expect(tauriMocks.codexAccountSwitch).toHaveBeenCalledWith("2"); expect(tauriMocks.refreshProviders).toHaveBeenCalledTimes(1); }); - it("uses opaque ordinal labels and matching tooltips while hideEmail is on", async () => { + it("uses an opaque ordinal only for the ambient account while hideEmail is on", async () => { const { container: hidden } = renderMenu(true, { accounts: [account("1", { source: "ambient" }), account("2")], accountOrdinals: { "1": 1, "2": 2 }, @@ -213,13 +213,23 @@ describe("CodexAccountsMenu", () => { hidden.querySelectorAll(".codex-menu-accounts__email").length, ).toBe(2); }); - const hiddenEmail = hidden.querySelectorAll( + const ambientLabel = hidden.querySelectorAll( + ".codex-menu-accounts__email", + )[0] as HTMLElement; + expect(ambientLabel.getAttribute("title")).toBe("Account 1"); + expect(ambientLabel.firstChild?.textContent).toBe("Account 1"); + expect(ambientLabel.firstChild?.textContent).not.toContain("@"); + expect(ambientLabel.firstChild?.textContent).not.toContain("example.com"); + + const managedEmail = hidden.querySelectorAll( ".codex-menu-accounts__email", )[1] as HTMLElement; - expect(hiddenEmail.getAttribute("title")).toBe(hiddenEmail.textContent); - expect(hiddenEmail.textContent).toBe("Account 2"); - expect(hiddenEmail.textContent).not.toContain("@"); - expect(hiddenEmail.textContent).not.toContain("example.com"); + expect(managedEmail.textContent).toBe("user-2@example.com"); + const switches = hidden.querySelectorAll( + ".codex-menu-accounts__switch", + ) as NodeListOf; + expect(switches[0].disabled).toBe(true); + expect(switches[1].disabled).toBe(false); const { container: visible } = renderMenu(false, { accounts: [account("1", { source: "ambient" }), account("2")], @@ -241,6 +251,7 @@ describe("CodexAccountsMenu", () => { const first = account("uuid-b", { emailHint: "alice@example.com", nickname: "team@example.com", + source: "ambient", }); const second = account("uuid-a", { emailHint: "bob@example.com", @@ -254,10 +265,10 @@ describe("CodexAccountsMenu", () => { }); await screen.findByText("Account 2"); const labels = container.querySelectorAll(".codex-menu-accounts__email"); - expect(labels[0].textContent).toBe("Account 2"); - expect(labels[1].textContent).toBe("Account 1"); - expect(labels[0].textContent).not.toContain("@"); - expect(labels[1].textContent).not.toContain("example.com"); + expect(labels[0].firstChild?.textContent).toBe("Account 2"); + expect(labels[1].textContent).toBe("bob@example.com — Private workspace"); + expect(labels[0].firstChild?.textContent).not.toContain("@"); + expect(labels[1].textContent).toContain("Private workspace"); }); }); diff --git a/apps/desktop-tauri/src/components/CodexAccountsMenu.tsx b/apps/desktop-tauri/src/components/CodexAccountsMenu.tsx index e12fa1ea7d..6aee681331 100644 --- a/apps/desktop-tauri/src/components/CodexAccountsMenu.tsx +++ b/apps/desktop-tauri/src/components/CodexAccountsMenu.tsx @@ -7,39 +7,13 @@ import type { } from "../types/bridge"; import { useLocale } from "../hooks/useLocale"; import { useFormattedResetTime } from "../hooks/useFormattedResetTime"; -import { buildCodexAccountDisplayNames } from "./codexAccountDisplay"; +import { buildCodexAccountSurfaceLabels } from "./codexAccountDisplay"; import { codexAccountSwitch, getCodexAccountsState, refreshProviders, } from "../lib/tauri"; -interface PrivateCodexAccountLabel { - label: string; - tooltip: string; -} - -/** - * Project a tray account label while keeping the privacy setting scoped to - * this switcher surface. The shared display-name builder remains unchanged so - * settings and other account-facing surfaces keep their existing behavior. - */ -function buildPrivateCodexAccountLabel( - account: CodexAccount, - displayName: string, - ordinal: number, - hidePersonalInfo: boolean, - accountWord: string, -): PrivateCodexAccountLabel { - if (hidePersonalInfo) { - const label = `${accountWord} ${ordinal}`; - return { label, tooltip: label }; - } - - const label = displayName || account.nickname || "Workspace"; - return { label, tooltip: label }; -} - /** * Multi-account lane surface for the Codex tray menu card (ADR 0003, * option A). Renders only when more than one Codex account exists, so the @@ -122,9 +96,12 @@ export default function CodexAccountsMenu({ return null; } - const accountDisplayNames = buildCodexAccountDisplayNames( + const accountDisplayNames = buildCodexAccountSurfaceLabels( accounts, displayNames, + accountOrdinals, + hideEmail, + t("Account"), ); return ( @@ -140,20 +117,14 @@ export default function CodexAccountsMenu({ )}