From 844ebea4c23ce0be02a305efb69bd1947b69a09f Mon Sep 17 00:00:00 2001 From: mrevanzak Date: Tue, 5 May 2026 13:08:56 +0700 Subject: [PATCH 1/5] feat(codex): show account beside plan --- plugins/codex/plugin.js | 4 +++ plugins/codex/plugin.json | 1 + plugins/codex/plugin.test.js | 1 + src/components/provider-card.test.tsx | 44 ++++++++++++++++++++++++++ src/components/provider-card.tsx | 45 +++++++++++++++++++++------ 5 files changed, 85 insertions(+), 10 deletions(-) diff --git a/plugins/codex/plugin.js b/plugins/codex/plugin.js index 24f89655..186c9629 100644 --- a/plugins/codex/plugin.js +++ b/plugins/codex/plugin.js @@ -679,6 +679,10 @@ lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" })) } + if (typeof accountId === "string" && accountId.trim()) { + lines.push(ctx.line.text({ label: "Account", value: accountId.trim() })) + } + return { plan: plan, lines: lines } } diff --git a/plugins/codex/plugin.json b/plugins/codex/plugin.json index b171ac99..4da33492 100644 --- a/plugins/codex/plugin.json +++ b/plugins/codex/plugin.json @@ -17,6 +17,7 @@ { "type": "progress", "label": "Spark Weekly", "scope": "detail" }, { "type": "progress", "label": "Reviews", "scope": "detail" }, { "type": "progress", "label": "Credits", "scope": "detail" }, + { "type": "text", "label": "Account", "scope": "detail" }, { "type": "text", "label": "Today", "scope": "detail" }, { "type": "text", "label": "Yesterday", "scope": "detail" }, { "type": "text", "label": "Last 30 Days", "scope": "detail" } diff --git a/plugins/codex/plugin.test.js b/plugins/codex/plugin.test.js index a1d7f905..e4851565 100644 --- a/plugins/codex/plugin.test.js +++ b/plugins/codex/plugin.test.js @@ -218,6 +218,7 @@ describe("codex plugin", () => { expect(result.plan).toBe("Pro 20x") expect(result.lines.find((line) => line.label === "Session")).toBeTruthy() expect(result.lines.find((line) => line.label === "Weekly")).toBeTruthy() + expect(result.lines.find((line) => line.label === "Account")?.value).toBe("acc") const credits = result.lines.find((line) => line.label === "Credits") expect(credits).toBeTruthy() expect(credits.used).toBe(900) diff --git a/src/components/provider-card.test.tsx b/src/components/provider-card.test.tsx index 5a1bc1fe..0b9d421f 100644 --- a/src/components/provider-card.test.tsx +++ b/src/components/provider-card.test.tsx @@ -110,6 +110,50 @@ describe("ProviderCard", () => { expect(screen.getByText("342 credits")).toBeInTheDocument() }) + it("shows account identity beside the plan badge", () => { + const { container } = render( + + ) + + const header = container.querySelector("h2")?.parentElement?.parentElement + expect(header).toBeTruthy() + expect(within(header as HTMLElement).getByText("Plus")).toBeInTheDocument() + const account = within(header as HTMLElement).getByText("dev@example.com") + expect(account.closest("[title]")).toHaveAttribute("title", "dev@example.com") + expect(screen.getAllByText("dev@example.com")).toHaveLength(1) + }) + + it("shows account identity in header when body is filtered to overview", () => { + const { container } = render( + + ) + + const header = container.querySelector("h2")?.parentElement?.parentElement + expect(header).toBeTruthy() + expect(within(header as HTMLElement).getByText("dev@example.com")).toBeInTheDocument() + expect(screen.queryByText("Account")).toBeNull() + }) + it("renders quick links and opens URL", async () => { render( { + const accountLine = lines.find( + (line) => line.type === "text" && line.label.toLowerCase() === "account" + ) + return accountLine?.type === "text" ? accountLine.value.trim() : "" + }, [lines]) + + const displayLines = useMemo( + () => filteredLines.filter( + (line) => !(line.type === "text" && line.label.toLowerCase() === "account") + ), + [filteredLines] + ) + // Format remaining cooldown time as "Xm Ys" const formatRemainingTime = () => { if (!lastManualRefreshAt) return "" @@ -247,15 +261,26 @@ export function ProviderCard({ ) )} - {plan && ( - - {plan} - - )} +
+ {accountIdentity && ( + + {accountIdentity} + + )} + {plan && ( + + {plan} + + )} +
{visibleLinks.length > 0 && (
@@ -302,7 +327,7 @@ export function ProviderCard({ {hasStaleData && (
- {groupLinesByType(filteredLines).map((group, gi) => + {groupLinesByType(displayLines).map((group, gi) => group.kind === "text" ? (
{group.lines.map((line, li) => ( From d577a80ebe502945229f707712628d294023cd1f Mon Sep 17 00:00:00 2001 From: mrevanzak Date: Tue, 5 May 2026 13:16:32 +0700 Subject: [PATCH 2/5] fix(codex): prefer email identity --- plugins/codex/plugin.js | 68 ++++++++++++++++++++++++++++++++++-- plugins/codex/plugin.test.js | 14 ++++++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/plugins/codex/plugin.js b/plugins/codex/plugin.js index 186c9629..dccc6a55 100644 --- a/plugins/codex/plugin.js +++ b/plugins/codex/plugin.js @@ -59,6 +59,69 @@ } } + function decodeBase64UrlUtf8(value) { + try { + let base64 = String(value).replace(/-/g, "+").replace(/_/g, "/") + while (base64.length % 4 !== 0) base64 += "=" + + let binary = null + if (typeof atob === "function") { + binary = atob(base64) + } else { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" + binary = "" + for (let i = 0; i < base64.length;) { + const e1 = chars.indexOf(base64.charAt(i++)) + const e2 = chars.indexOf(base64.charAt(i++)) + const e3 = chars.indexOf(base64.charAt(i++)) + const e4 = chars.indexOf(base64.charAt(i++)) + if (e1 < 0 || e2 < 0) return null + binary += String.fromCharCode((e1 << 2) | (e2 >> 4)) + if (e3 !== 64 && e3 >= 0) binary += String.fromCharCode(((e2 & 15) << 4) | (e3 >> 2)) + if (e4 !== 64 && e4 >= 0) binary += String.fromCharCode(((e3 & 3) << 6) | e4) + } + } + + const bytes = [] + for (let i = 0; i < binary.length; i++) bytes.push(binary.charCodeAt(i)) + if (typeof TextDecoder !== "undefined") { + return new TextDecoder("utf-8", { fatal: false }).decode(new Uint8Array(bytes)) + } + return decodeURIComponent(binary.split("").map((c) => { + const h = c.charCodeAt(0).toString(16) + return "%" + (h.length === 1 ? "0" + h : h) + }).join("")) + } catch {} + return null + } + + function readString(value) { + return typeof value === "string" && value.trim() ? value.trim() : null + } + + function parseIdTokenClaims(ctx, idToken) { + if (!idToken) return null + if (typeof idToken === "object") return idToken + if (typeof idToken !== "string") return null + const parts = idToken.split(".") + if (parts.length < 2) return null + const payload = decodeBase64UrlUtf8(parts[1]) + return payload ? ctx.util.tryParseJson(payload) : null + } + + function getCodexAccountIdentity(ctx, auth) { + const tokens = auth && auth.tokens ? auth.tokens : null + if (!tokens) return null + + const claims = parseIdTokenClaims(ctx, tokens.id_token) + const email = readString(claims?.email) || + readString(claims?.profile?.email) || + readString(claims?.["https://api.openai.com/profile.email"]) + if (email) return email + + return readString(tokens.account_id) + } + function tryParseAuthJson(ctx, text) { if (!text) return null const parsed = ctx.util.tryParseJson(text) @@ -441,6 +504,7 @@ const nowMs = Date.now() let accessToken = auth.tokens.access_token const accountId = auth.tokens.account_id + const accountIdentity = getCodexAccountIdentity(ctx, auth) if (needsRefresh(ctx, auth, nowMs)) { ctx.host.log.info("token needs refresh (age > " + (REFRESH_AGE_MS / 1000 / 60 / 60 / 24) + " days)") @@ -679,8 +743,8 @@ lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" })) } - if (typeof accountId === "string" && accountId.trim()) { - lines.push(ctx.line.text({ label: "Account", value: accountId.trim() })) + if (accountIdentity) { + lines.push(ctx.line.text({ label: "Account", value: accountIdentity })) } return { plan: plan, lines: lines } diff --git a/plugins/codex/plugin.test.js b/plugins/codex/plugin.test.js index e4851565..c60d5561 100644 --- a/plugins/codex/plugin.test.js +++ b/plugins/codex/plugin.test.js @@ -6,6 +6,11 @@ const loadPlugin = async () => { return globalThis.__openusage_plugin } +const jwtWithPayload = (payload) => { + const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url") + return encode({ alg: "none" }) + "." + encode(payload) + "." +} + describe("codex plugin", () => { beforeEach(() => { delete globalThis.__openusage_plugin @@ -189,7 +194,12 @@ describe("codex plugin", () => { const ctx = makeCtx() const authPath = "~/.codex/auth.json" ctx.host.fs.writeText(authPath, JSON.stringify({ - tokens: { access_token: "old", refresh_token: "refresh", account_id: "acc" }, + tokens: { + access_token: "old", + refresh_token: "refresh", + account_id: "acc", + id_token: jwtWithPayload({ email: "dev@example.com" }), + }, last_refresh: "2000-01-01T00:00:00.000Z", })) ctx.host.http.request.mockImplementation((opts) => { @@ -218,7 +228,7 @@ describe("codex plugin", () => { expect(result.plan).toBe("Pro 20x") expect(result.lines.find((line) => line.label === "Session")).toBeTruthy() expect(result.lines.find((line) => line.label === "Weekly")).toBeTruthy() - expect(result.lines.find((line) => line.label === "Account")?.value).toBe("acc") + expect(result.lines.find((line) => line.label === "Account")?.value).toBe("dev@example.com") const credits = result.lines.find((line) => line.label === "Credits") expect(credits).toBeTruthy() expect(credits.used).toBe(900) From d9d01673cea8a5b5aaa2361bbe819eccbcdef4a2 Mon Sep 17 00:00:00 2001 From: mrevanzak Date: Tue, 5 May 2026 13:30:39 +0700 Subject: [PATCH 3/5] fix(codex): refresh account identity --- plugins/codex/plugin.js | 2 +- plugins/codex/plugin.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/plugins/codex/plugin.js b/plugins/codex/plugin.js index dccc6a55..9d30cf93 100644 --- a/plugins/codex/plugin.js +++ b/plugins/codex/plugin.js @@ -504,7 +504,6 @@ const nowMs = Date.now() let accessToken = auth.tokens.access_token const accountId = auth.tokens.account_id - const accountIdentity = getCodexAccountIdentity(ctx, auth) if (needsRefresh(ctx, auth, nowMs)) { ctx.host.log.info("token needs refresh (age > " + (REFRESH_AGE_MS / 1000 / 60 / 60 / 24) + " days)") @@ -743,6 +742,7 @@ lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" })) } + const accountIdentity = getCodexAccountIdentity(ctx, auth) if (accountIdentity) { lines.push(ctx.line.text({ label: "Account", value: accountIdentity })) } diff --git a/plugins/codex/plugin.test.js b/plugins/codex/plugin.test.js index c60d5561..e0527009 100644 --- a/plugins/codex/plugin.test.js +++ b/plugins/codex/plugin.test.js @@ -234,6 +234,34 @@ describe("codex plugin", () => { expect(credits.used).toBe(900) }) + it("uses account email from refreshed id token", async () => { + const ctx = makeCtx() + ctx.host.fs.writeText("~/.codex/auth.json", JSON.stringify({ + tokens: { access_token: "old", refresh_token: "refresh", account_id: "acc" }, + last_refresh: "2000-01-01T00:00:00.000Z", + })) + ctx.host.http.request.mockImplementation((opts) => { + if (String(opts.url).includes("oauth/token")) { + return { + status: 200, + bodyText: JSON.stringify({ + access_token: "new", + id_token: jwtWithPayload({ email: "fresh@example.com" }), + }), + } + } + return { + status: 200, + headers: { "x-codex-primary-used-percent": "25" }, + bodyText: JSON.stringify({}), + } + }) + + const plugin = await loadPlugin() + const result = plugin.probe(ctx) + expect(result.lines.find((line) => line.label === "Account")?.value).toBe("fresh@example.com") + }) + it("maps prolite plan to Pro 5x", async () => { const ctx = makeCtx() ctx.host.fs.writeText("~/.codex/auth.json", JSON.stringify({ From d2f78eb22478c8d0ee63e6d3a14c2262d1284fcc Mon Sep 17 00:00:00 2001 From: mrevanzak Date: Tue, 5 May 2026 19:41:00 +0700 Subject: [PATCH 4/5] fix(provider-card): respect account scope --- docs/plugins/api.md | 2 ++ docs/plugins/schema.md | 2 ++ plugins/codex/plugin.json | 1 - plugins/gemini/plugin.json | 3 +-- src/components/provider-card.test.tsx | 25 ++++++++++++++++++++++++- src/components/provider-card.tsx | 4 ++-- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/plugins/api.md b/docs/plugins/api.md index 7e96a3e8..9903ff3b 100644 --- a/docs/plugins/api.md +++ b/docs/plugins/api.md @@ -365,6 +365,8 @@ ctx.line.text({ label: "Account", value: "user@example.com" }) ctx.line.text({ label: "Status", value: "Active", color: "#22c55e", subtitle: "Since Jan 2024" }) ``` +Note: `label: "Account"` is reserved. The UI displays it in the provider card header instead of the normal body line. + ### `ctx.line.progress(opts)` Creates a progress bar line. diff --git a/docs/plugins/schema.md b/docs/plugins/schema.md index c2e9ddb3..f8a037d1 100644 --- a/docs/plugins/schema.md +++ b/docs/plugins/schema.md @@ -177,6 +177,8 @@ ctx.line.text({ label: "Account", value: "user@example.com" }) ctx.line.text({ label: "Status", value: "Active", color: "#22c55e", subtitle: "Since Jan 2024" }) ``` +Note: `label: "Account"` is reserved. The UI displays it in the provider card header instead of the normal body line. + ### Progress Line Shows a progress bar with optional formatting. diff --git a/plugins/codex/plugin.json b/plugins/codex/plugin.json index 4da33492..b171ac99 100644 --- a/plugins/codex/plugin.json +++ b/plugins/codex/plugin.json @@ -17,7 +17,6 @@ { "type": "progress", "label": "Spark Weekly", "scope": "detail" }, { "type": "progress", "label": "Reviews", "scope": "detail" }, { "type": "progress", "label": "Credits", "scope": "detail" }, - { "type": "text", "label": "Account", "scope": "detail" }, { "type": "text", "label": "Today", "scope": "detail" }, { "type": "text", "label": "Yesterday", "scope": "detail" }, { "type": "text", "label": "Last 30 Days", "scope": "detail" } diff --git a/plugins/gemini/plugin.json b/plugins/gemini/plugin.json index 22a9fee9..a3678bf4 100644 --- a/plugins/gemini/plugin.json +++ b/plugins/gemini/plugin.json @@ -8,7 +8,6 @@ "brandColor": "#4285F4", "lines": [ { "type": "progress", "label": "Pro", "scope": "overview", "primaryOrder": 1 }, - { "type": "progress", "label": "Flash", "scope": "overview", "primaryOrder": 2 }, - { "type": "text", "label": "Account", "scope": "detail" } + { "type": "progress", "label": "Flash", "scope": "overview", "primaryOrder": 2 } ] } diff --git a/src/components/provider-card.test.tsx b/src/components/provider-card.test.tsx index 0b9d421f..e0b166c4 100644 --- a/src/components/provider-card.test.tsx +++ b/src/components/provider-card.test.tsx @@ -130,7 +130,7 @@ describe("ProviderCard", () => { expect(screen.getAllByText("dev@example.com")).toHaveLength(1) }) - it("shows account identity in header when body is filtered to overview", () => { + it("hides detail-scoped account identity when filtered to overview", () => { const { container } = render( { /> ) + const header = container.querySelector("h2")?.parentElement?.parentElement + expect(header).toBeTruthy() + expect(within(header as HTMLElement).queryByText("dev@example.com")).toBeNull() + expect(screen.queryByText("Account")).toBeNull() + }) + + it("shows detail-scoped account identity in header on detail cards", () => { + const { container } = render( + + ) + const header = container.querySelector("h2")?.parentElement?.parentElement expect(header).toBeTruthy() expect(within(header as HTMLElement).getByText("dev@example.com")).toBeInTheDocument() diff --git a/src/components/provider-card.tsx b/src/components/provider-card.tsx index 6ac51766..881105aa 100644 --- a/src/components/provider-card.tsx +++ b/src/components/provider-card.tsx @@ -165,11 +165,11 @@ export function ProviderCard({ ) const accountIdentity = useMemo(() => { - const accountLine = lines.find( + const accountLine = filteredLines.find( (line) => line.type === "text" && line.label.toLowerCase() === "account" ) return accountLine?.type === "text" ? accountLine.value.trim() : "" - }, [lines]) + }, [filteredLines]) const displayLines = useMemo( () => filteredLines.filter( From 7250a8c09d05ec53b46209d7786085d5527371cf Mon Sep 17 00:00:00 2001 From: mrevanzak Date: Tue, 5 May 2026 19:41:36 +0700 Subject: [PATCH 5/5] feat(settings): toggle account identity --- src/App.test.tsx | 8 +++++++ src/App.tsx | 6 +++++ src/components/app/app-content.test.tsx | 19 ++++++++++++++++ src/components/app/app-content.tsx | 8 +++++++ src/components/provider-card.test.tsx | 17 ++++++++++++++ src/components/provider-card.tsx | 4 +++- src/hooks/app/use-settings-bootstrap.test.ts | 17 ++++++++++++++ src/hooks/app/use-settings-bootstrap.ts | 13 +++++++++++ .../app/use-settings-display-actions.test.ts | 16 ++++++++++++++ src/hooks/app/use-settings-display-actions.ts | 12 ++++++++++ src/lib/settings.test.ts | 22 +++++++++++++++++++ src/lib/settings.ts | 13 +++++++++++ src/pages/overview.tsx | 3 +++ src/pages/provider-detail.tsx | 3 +++ src/pages/settings.test.tsx | 15 +++++++++++++ src/pages/settings.tsx | 18 +++++++++++++++ src/stores/app-preferences-store.ts | 5 +++++ 17 files changed, 198 insertions(+), 1 deletion(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 9c54a0d0..e7637fe4 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -27,6 +27,8 @@ const state = vi.hoisted(() => ({ saveGlobalShortcutMock: vi.fn(), loadStartOnLoginMock: vi.fn(), saveStartOnLoginMock: vi.fn(), + loadShowAccountIdentityMock: vi.fn(), + saveShowAccountIdentityMock: vi.fn(), autostartEnableMock: vi.fn(), autostartDisableMock: vi.fn(), autostartIsEnabledMock: vi.fn(), @@ -239,6 +241,8 @@ vi.mock("@/lib/settings", async () => { saveGlobalShortcut: state.saveGlobalShortcutMock, loadStartOnLogin: state.loadStartOnLoginMock, saveStartOnLogin: state.saveStartOnLoginMock, + loadShowAccountIdentity: state.loadShowAccountIdentityMock, + saveShowAccountIdentity: state.saveShowAccountIdentityMock, } }) @@ -278,6 +282,8 @@ describe("App", () => { state.saveGlobalShortcutMock.mockReset() state.loadStartOnLoginMock.mockReset() state.saveStartOnLoginMock.mockReset() + state.loadShowAccountIdentityMock.mockReset() + state.saveShowAccountIdentityMock.mockReset() state.autostartEnableMock.mockReset() state.autostartDisableMock.mockReset() state.autostartIsEnabledMock.mockReset() @@ -316,6 +322,8 @@ describe("App", () => { state.saveGlobalShortcutMock.mockResolvedValue(undefined) state.loadStartOnLoginMock.mockResolvedValue(false) state.saveStartOnLoginMock.mockResolvedValue(undefined) + state.loadShowAccountIdentityMock.mockResolvedValue(true) + state.saveShowAccountIdentityMock.mockResolvedValue(undefined) state.autostartEnableMock.mockResolvedValue(undefined) state.autostartDisableMock.mockResolvedValue(undefined) state.autostartIsEnabledMock.mockResolvedValue(false) diff --git a/src/App.tsx b/src/App.tsx index d5edc031..9ce3a9ec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -58,6 +58,7 @@ function App() { setResetTimerDisplayMode, setGlobalShortcut, setStartOnLogin, + setShowAccountIdentity, } = useAppPreferencesStore( useShallow((state) => ({ autoUpdateInterval: state.autoUpdateInterval, @@ -72,6 +73,7 @@ function App() { setResetTimerDisplayMode: state.setResetTimerDisplayMode, setGlobalShortcut: state.setGlobalShortcut, setStartOnLogin: state.setStartOnLogin, + setShowAccountIdentity: state.setShowAccountIdentity, })) ) @@ -120,6 +122,7 @@ function App() { setResetTimerDisplayMode, setGlobalShortcut, setStartOnLogin, + setShowAccountIdentity, setLoadingForPlugins, setErrorForPlugins, startBatch, @@ -133,11 +136,13 @@ function App() { handleResetTimerDisplayModeChange, handleResetTimerDisplayModeToggle, handleMenubarIconStyleChange, + handleShowAccountIdentityChange, } = useSettingsDisplayActions({ setThemeMode, setDisplayMode, resetTimerDisplayMode, setResetTimerDisplayMode, + setShowAccountIdentity, setMenubarIconStyle, scheduleTrayIconUpdate, }) @@ -247,6 +252,7 @@ function App() { onResetTimerDisplayModeChange: handleResetTimerDisplayModeChange, onResetTimerDisplayModeToggle: handleResetTimerDisplayModeToggle, onMenubarIconStyleChange: handleMenubarIconStyleChange, + onShowAccountIdentityChange: handleShowAccountIdentityChange, traySettingsPreview, onGlobalShortcutChange: handleGlobalShortcutChange, onStartOnLoginChange: handleStartOnLoginChange, diff --git a/src/components/app/app-content.test.tsx b/src/components/app/app-content.test.tsx index 0cce5238..b6b8e32c 100644 --- a/src/components/app/app-content.test.tsx +++ b/src/components/app/app-content.test.tsx @@ -63,8 +63,16 @@ function createProps(): AppContentProps { onDisplayModeChange: vi.fn(), onResetTimerDisplayModeChange: vi.fn(), onResetTimerDisplayModeToggle: vi.fn(), + onMenubarIconStyleChange: vi.fn(), + traySettingsPreview: { + bars: [], + providerBars: [], + providerIconUrl: null, + providerPercentText: "", + }, onGlobalShortcutChange: vi.fn(), onStartOnLoginChange: vi.fn(), + onShowAccountIdentityChange: vi.fn(), } } @@ -93,6 +101,17 @@ describe("AppContent", () => { expect(settingsPageMock).toHaveBeenCalledTimes(1) }) + it("passes account identity visibility to child pages", () => { + const prefs = useAppPreferencesStore.getState() + prefs.setShowAccountIdentity(false) + + useAppUiStore.getState().setActiveView("settings") + render() + expect(settingsPageMock).toHaveBeenCalledWith( + expect.objectContaining({ showAccountIdentity: false }) + ) + }) + it("passes retry callback for provider detail view", () => { const props = createProps() useAppUiStore.getState().setActiveView("codex") diff --git a/src/components/app/app-content.tsx b/src/components/app/app-content.tsx index e362fa76..dcab3f0b 100644 --- a/src/components/app/app-content.tsx +++ b/src/components/app/app-content.tsx @@ -35,6 +35,7 @@ export type AppContentActionProps = { traySettingsPreview: TraySettingsPreview onGlobalShortcutChange: (value: GlobalShortcut) => void onStartOnLoginChange: (value: boolean) => void + onShowAccountIdentityChange: (value: boolean) => void } export type AppContentProps = AppContentDerivedProps & AppContentActionProps @@ -55,6 +56,7 @@ export function AppContent({ traySettingsPreview, onGlobalShortcutChange, onStartOnLoginChange, + onShowAccountIdentityChange, }: AppContentProps) { const { activeView } = useAppUiStore( useShallow((state) => ({ @@ -70,6 +72,7 @@ export function AppContent({ globalShortcut, themeMode, startOnLogin, + showAccountIdentity, } = useAppPreferencesStore( useShallow((state) => ({ displayMode: state.displayMode, @@ -79,6 +82,7 @@ export function AppContent({ globalShortcut: state.globalShortcut, themeMode: state.themeMode, startOnLogin: state.startOnLogin, + showAccountIdentity: state.showAccountIdentity, })) ) @@ -90,6 +94,7 @@ export function AppContent({ displayMode={displayMode} resetTimerDisplayMode={resetTimerDisplayMode} onResetTimerDisplayModeToggle={onResetTimerDisplayModeToggle} + showAccountIdentity={showAccountIdentity} /> ) } @@ -115,6 +120,8 @@ export function AppContent({ onGlobalShortcutChange={onGlobalShortcutChange} startOnLogin={startOnLogin} onStartOnLoginChange={onStartOnLoginChange} + showAccountIdentity={showAccountIdentity} + onShowAccountIdentityChange={onShowAccountIdentityChange} /> ) } @@ -130,6 +137,7 @@ export function AppContent({ displayMode={displayMode} resetTimerDisplayMode={resetTimerDisplayMode} onResetTimerDisplayModeToggle={onResetTimerDisplayModeToggle} + showAccountIdentity={showAccountIdentity} /> ) } diff --git a/src/components/provider-card.test.tsx b/src/components/provider-card.test.tsx index e0b166c4..5c13bb16 100644 --- a/src/components/provider-card.test.tsx +++ b/src/components/provider-card.test.tsx @@ -177,6 +177,23 @@ describe("ProviderCard", () => { expect(screen.queryByText("Account")).toBeNull() }) + it("hides account identity when disabled", () => { + render( + + ) + + expect(screen.queryByText("dev@example.com")).not.toBeInTheDocument() + expect(screen.getByText("Plus")).toBeInTheDocument() + }) + it("renders quick links and opens URL", async () => { render( void + showAccountIdentity?: boolean } const PACE_VISUALS: Record = { @@ -107,6 +108,7 @@ export function ProviderCard({ displayMode, resetTimerDisplayMode = "relative", onResetTimerDisplayModeToggle, + showAccountIdentity = true, }: ProviderCardProps) { const cooldownRemainingMs = useMemo(() => { if (!lastManualRefreshAt) return 0 @@ -262,7 +264,7 @@ export function ProviderCard({ )}
- {accountIdentity && ( + {showAccountIdentity && accountIdentity && ( ({ DEFAULT_GLOBAL_SHORTCUT: null, DEFAULT_MENUBAR_ICON_STYLE: "provider", DEFAULT_RESET_TIMER_DISPLAY_MODE: "relative", + DEFAULT_SHOW_ACCOUNT_IDENTITY: true, DEFAULT_START_ON_LOGIN: false, DEFAULT_THEME_MODE: "system", getEnabledPluginIds: getEnabledPluginIdsMock, @@ -68,6 +71,7 @@ vi.mock("@/lib/settings", () => ({ loadMenubarIconStyle: loadMenubarIconStyleMock, loadPluginSettings: loadPluginSettingsMock, loadResetTimerDisplayMode: loadResetTimerDisplayModeMock, + loadShowAccountIdentity: loadShowAccountIdentityMock, loadStartOnLogin: loadStartOnLoginMock, loadThemeMode: loadThemeModeMock, migrateLegacyTraySettings: migrateLegacyTraySettingsMock, @@ -87,6 +91,7 @@ function createArgs() { setResetTimerDisplayMode: vi.fn(), setGlobalShortcut: vi.fn(), setStartOnLogin: vi.fn(), + setShowAccountIdentity: vi.fn(), setMenubarIconStyle: vi.fn(), setLoadingForPlugins: vi.fn(), setErrorForPlugins: vi.fn(), @@ -109,6 +114,7 @@ describe("useSettingsBootstrap", () => { loadMenubarIconStyleMock.mockReset() loadPluginSettingsMock.mockReset() loadResetTimerDisplayModeMock.mockReset() + loadShowAccountIdentityMock.mockReset() loadStartOnLoginMock.mockReset() loadThemeModeMock.mockReset() migrateLegacyTraySettingsMock.mockReset() @@ -137,6 +143,7 @@ describe("useSettingsBootstrap", () => { loadGlobalShortcutMock.mockResolvedValue("CommandOrControl+Shift+O") loadMenubarIconStyleMock.mockResolvedValue("provider") loadStartOnLoginMock.mockResolvedValue(true) + loadShowAccountIdentityMock.mockResolvedValue(false) migrateLegacyTraySettingsMock.mockResolvedValue(undefined) savePluginSettingsMock.mockResolvedValue(undefined) getEnabledPluginIdsMock.mockReturnValue(["codex"]) @@ -170,4 +177,14 @@ describe("useSettingsBootstrap", () => { errorSpy.mockRestore() }) + + it("loads account identity visibility", async () => { + const args = createArgs() + + renderHook(() => useSettingsBootstrap(args)) + + await waitFor(() => { + expect(args.setShowAccountIdentity).toHaveBeenCalledWith(false) + }) + }) }) diff --git a/src/hooks/app/use-settings-bootstrap.ts b/src/hooks/app/use-settings-bootstrap.ts index fcc7df09..f1806495 100644 --- a/src/hooks/app/use-settings-bootstrap.ts +++ b/src/hooks/app/use-settings-bootstrap.ts @@ -13,6 +13,7 @@ import { DEFAULT_GLOBAL_SHORTCUT, DEFAULT_MENUBAR_ICON_STYLE, DEFAULT_RESET_TIMER_DISPLAY_MODE, + DEFAULT_SHOW_ACCOUNT_IDENTITY, DEFAULT_START_ON_LOGIN, DEFAULT_THEME_MODE, getEnabledPluginIds, @@ -23,6 +24,7 @@ import { migrateLegacyTraySettings, loadPluginSettings, loadResetTimerDisplayMode, + loadShowAccountIdentity, loadStartOnLogin, loadThemeMode, normalizePluginSettings, @@ -45,6 +47,7 @@ type UseSettingsBootstrapArgs = { setResetTimerDisplayMode: (value: ResetTimerDisplayMode) => void setGlobalShortcut: (value: GlobalShortcut) => void setStartOnLogin: (value: boolean) => void + setShowAccountIdentity: (value: boolean) => void setMenubarIconStyle: (value: MenubarIconStyle) => void setLoadingForPlugins: (ids: string[]) => void setErrorForPlugins: (ids: string[], error: string) => void @@ -60,6 +63,7 @@ export function useSettingsBootstrap({ setResetTimerDisplayMode, setGlobalShortcut, setStartOnLogin, + setShowAccountIdentity, setMenubarIconStyle, setLoadingForPlugins, setErrorForPlugins, @@ -135,6 +139,13 @@ export function useSettingsBootstrap({ console.error("Failed to load start on login:", error) } + let storedShowAccountIdentity = DEFAULT_SHOW_ACCOUNT_IDENTITY + try { + storedShowAccountIdentity = await loadShowAccountIdentity() + } catch (error) { + console.error("Failed to load account identity visibility:", error) + } + try { await applyStartOnLogin(storedStartOnLogin) } catch (error) { @@ -161,6 +172,7 @@ export function useSettingsBootstrap({ setResetTimerDisplayMode(storedResetTimerDisplayMode) setGlobalShortcut(storedGlobalShortcut) setStartOnLogin(storedStartOnLogin) + setShowAccountIdentity(storedShowAccountIdentity) setMenubarIconStyle(storedMenubarIconStyle) const enabledIds = getEnabledPluginIds(normalized) @@ -197,6 +209,7 @@ export function useSettingsBootstrap({ setPluginsMeta, setResetTimerDisplayMode, setStartOnLogin, + setShowAccountIdentity, setThemeMode, startBatch, ]) diff --git a/src/hooks/app/use-settings-display-actions.test.ts b/src/hooks/app/use-settings-display-actions.test.ts index 7d0db6a4..abe5c362 100644 --- a/src/hooks/app/use-settings-display-actions.test.ts +++ b/src/hooks/app/use-settings-display-actions.test.ts @@ -5,12 +5,14 @@ const { trackMock, saveDisplayModeMock, saveResetTimerDisplayModeMock, + saveShowAccountIdentityMock, saveThemeModeMock, } = vi.hoisted(() => ({ trackMock: vi.fn(), saveThemeModeMock: vi.fn(), saveDisplayModeMock: vi.fn(), saveResetTimerDisplayModeMock: vi.fn(), + saveShowAccountIdentityMock: vi.fn(), })) vi.mock("@/lib/analytics", () => ({ @@ -21,6 +23,7 @@ vi.mock("@/lib/settings", () => ({ saveThemeMode: saveThemeModeMock, saveDisplayMode: saveDisplayModeMock, saveResetTimerDisplayMode: saveResetTimerDisplayModeMock, + saveShowAccountIdentity: saveShowAccountIdentityMock, })) import { useSettingsDisplayActions } from "@/hooks/app/use-settings-display-actions" @@ -31,15 +34,18 @@ describe("useSettingsDisplayActions", () => { saveThemeModeMock.mockReset() saveDisplayModeMock.mockReset() saveResetTimerDisplayModeMock.mockReset() + saveShowAccountIdentityMock.mockReset() saveThemeModeMock.mockResolvedValue(undefined) saveDisplayModeMock.mockResolvedValue(undefined) saveResetTimerDisplayModeMock.mockResolvedValue(undefined) + saveShowAccountIdentityMock.mockResolvedValue(undefined) }) it("tracks and applies display-related setting changes", () => { const setThemeMode = vi.fn() const setDisplayMode = vi.fn() const setResetTimerDisplayMode = vi.fn() + const setShowAccountIdentity = vi.fn() const scheduleTrayIconUpdate = vi.fn() const { result } = renderHook(() => @@ -48,6 +54,7 @@ describe("useSettingsDisplayActions", () => { setDisplayMode, resetTimerDisplayMode: "relative", setResetTimerDisplayMode, + setShowAccountIdentity, scheduleTrayIconUpdate, }) ) @@ -56,6 +63,7 @@ describe("useSettingsDisplayActions", () => { result.current.handleThemeModeChange("dark") result.current.handleDisplayModeChange("used") result.current.handleResetTimerDisplayModeChange("absolute") + result.current.handleShowAccountIdentityChange(false) }) expect(trackMock).toHaveBeenCalledWith("setting_changed", { setting: "theme", value: "dark" }) @@ -71,11 +79,13 @@ describe("useSettingsDisplayActions", () => { expect(setThemeMode).toHaveBeenCalledWith("dark") expect(setDisplayMode).toHaveBeenCalledWith("used") expect(setResetTimerDisplayMode).toHaveBeenCalledWith("absolute") + expect(setShowAccountIdentity).toHaveBeenCalledWith(false) expect(scheduleTrayIconUpdate).toHaveBeenCalledWith("settings", 0) expect(saveThemeModeMock).toHaveBeenCalledWith("dark") expect(saveDisplayModeMock).toHaveBeenCalledWith("used") expect(saveResetTimerDisplayModeMock).toHaveBeenCalledWith("absolute") + expect(saveShowAccountIdentityMock).toHaveBeenCalledWith(false) }) it("toggles reset timer mode in both directions", () => { @@ -88,6 +98,7 @@ describe("useSettingsDisplayActions", () => { setDisplayMode: vi.fn(), resetTimerDisplayMode: mode, setResetTimerDisplayMode, + setShowAccountIdentity: vi.fn(), scheduleTrayIconUpdate: vi.fn(), }), { initialProps: { mode: "relative" as const } } @@ -109,10 +120,12 @@ describe("useSettingsDisplayActions", () => { const themeError = new Error("theme failed") const displayError = new Error("display failed") const resetError = new Error("reset failed") + const accountError = new Error("account failed") const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) saveThemeModeMock.mockRejectedValueOnce(themeError) saveDisplayModeMock.mockRejectedValueOnce(displayError) saveResetTimerDisplayModeMock.mockRejectedValueOnce(resetError) + saveShowAccountIdentityMock.mockRejectedValueOnce(accountError) const { result } = renderHook(() => useSettingsDisplayActions({ @@ -120,6 +133,7 @@ describe("useSettingsDisplayActions", () => { setDisplayMode: vi.fn(), resetTimerDisplayMode: "relative", setResetTimerDisplayMode: vi.fn(), + setShowAccountIdentity: vi.fn(), scheduleTrayIconUpdate: vi.fn(), }) ) @@ -128,12 +142,14 @@ describe("useSettingsDisplayActions", () => { result.current.handleThemeModeChange("light") result.current.handleDisplayModeChange("left") result.current.handleResetTimerDisplayModeChange("relative") + result.current.handleShowAccountIdentityChange(true) }) await waitFor(() => { expect(errorSpy).toHaveBeenCalledWith("Failed to save theme mode:", themeError) expect(errorSpy).toHaveBeenCalledWith("Failed to save display mode:", displayError) expect(errorSpy).toHaveBeenCalledWith("Failed to save reset timer display mode:", resetError) + expect(errorSpy).toHaveBeenCalledWith("Failed to save account identity visibility:", accountError) }) errorSpy.mockRestore() diff --git a/src/hooks/app/use-settings-display-actions.ts b/src/hooks/app/use-settings-display-actions.ts index 65dcc886..16cd173c 100644 --- a/src/hooks/app/use-settings-display-actions.ts +++ b/src/hooks/app/use-settings-display-actions.ts @@ -4,6 +4,7 @@ import { saveDisplayMode, saveMenubarIconStyle, saveResetTimerDisplayMode, + saveShowAccountIdentity, saveThemeMode, type DisplayMode, type MenubarIconStyle, @@ -18,6 +19,7 @@ type UseSettingsDisplayActionsArgs = { setDisplayMode: (value: DisplayMode) => void resetTimerDisplayMode: ResetTimerDisplayMode setResetTimerDisplayMode: (value: ResetTimerDisplayMode) => void + setShowAccountIdentity: (value: boolean) => void setMenubarIconStyle: (value: MenubarIconStyle) => void scheduleTrayIconUpdate: ScheduleTrayIconUpdate } @@ -27,6 +29,7 @@ export function useSettingsDisplayActions({ setDisplayMode, resetTimerDisplayMode, setResetTimerDisplayMode, + setShowAccountIdentity, setMenubarIconStyle, scheduleTrayIconUpdate, }: UseSettingsDisplayActionsArgs) { @@ -69,11 +72,20 @@ export function useSettingsDisplayActions({ }) }, [scheduleTrayIconUpdate, setMenubarIconStyle]) + const handleShowAccountIdentityChange = useCallback((value: boolean) => { + track("setting_changed", { setting: "show_account_identity", value: String(value) }) + setShowAccountIdentity(value) + void saveShowAccountIdentity(value).catch((error) => { + console.error("Failed to save account identity visibility:", error) + }) + }, [setShowAccountIdentity]) + return { handleThemeModeChange, handleDisplayModeChange, handleResetTimerDisplayModeChange, handleResetTimerDisplayModeToggle, handleMenubarIconStyleChange, + handleShowAccountIdentityChange, } } diff --git a/src/lib/settings.test.ts b/src/lib/settings.test.ts index 1c686863..652f3d8d 100644 --- a/src/lib/settings.test.ts +++ b/src/lib/settings.test.ts @@ -6,6 +6,7 @@ import { DEFAULT_MENUBAR_ICON_STYLE, DEFAULT_PLUGIN_SETTINGS, DEFAULT_RESET_TIMER_DISPLAY_MODE, + DEFAULT_SHOW_ACCOUNT_IDENTITY, DEFAULT_START_ON_LOGIN, DEFAULT_THEME_MODE, arePluginSettingsEqual, @@ -16,6 +17,7 @@ import { loadMenubarIconStyle, loadPluginSettings, loadResetTimerDisplayMode, + loadShowAccountIdentity, loadStartOnLogin, migrateLegacyTraySettings, loadThemeMode, @@ -26,6 +28,7 @@ import { saveMenubarIconStyle, savePluginSettings, saveResetTimerDisplayMode, + saveShowAccountIdentity, saveStartOnLogin, saveThemeMode, } from "@/lib/settings" @@ -339,4 +342,23 @@ describe("settings", () => { storeState.set("startOnLogin", "invalid") await expect(loadStartOnLogin()).resolves.toBe(DEFAULT_START_ON_LOGIN) }) + + it("loads default account identity visibility when missing", async () => { + await expect(loadShowAccountIdentity()).resolves.toBe(DEFAULT_SHOW_ACCOUNT_IDENTITY) + }) + + it("loads stored account identity visibility", async () => { + storeState.set("showAccountIdentity", false) + await expect(loadShowAccountIdentity()).resolves.toBe(false) + }) + + it("saves account identity visibility", async () => { + await saveShowAccountIdentity(false) + await expect(loadShowAccountIdentity()).resolves.toBe(false) + }) + + it("falls back to default for invalid account identity visibility", async () => { + storeState.set("showAccountIdentity", "invalid") + await expect(loadShowAccountIdentity()).resolves.toBe(DEFAULT_SHOW_ACCOUNT_IDENTITY) + }) }) diff --git a/src/lib/settings.ts b/src/lib/settings.ts index a94d0a7c..fdec41c2 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -33,6 +33,7 @@ const LEGACY_TRAY_ICON_STYLE_KEY = "trayIconStyle"; const LEGACY_TRAY_SHOW_PERCENTAGE_KEY = "trayShowPercentage"; const GLOBAL_SHORTCUT_KEY = "globalShortcut"; const START_ON_LOGIN_KEY = "startOnLogin"; +const SHOW_ACCOUNT_IDENTITY_KEY = "showAccountIdentity"; export const DEFAULT_AUTO_UPDATE_INTERVAL: AutoUpdateIntervalMinutes = 15; export const DEFAULT_THEME_MODE: ThemeMode = "system"; @@ -41,6 +42,7 @@ export const DEFAULT_RESET_TIMER_DISPLAY_MODE: ResetTimerDisplayMode = "relative export const DEFAULT_MENUBAR_ICON_STYLE: MenubarIconStyle = "provider"; export const DEFAULT_GLOBAL_SHORTCUT: GlobalShortcut = null; export const DEFAULT_START_ON_LOGIN = false; +export const DEFAULT_SHOW_ACCOUNT_IDENTITY = true; const AUTO_UPDATE_INTERVALS: AutoUpdateIntervalMinutes[] = [5, 15, 30, 60]; const THEME_MODES: ThemeMode[] = ["system", "light", "dark"]; @@ -303,3 +305,14 @@ export async function saveStartOnLogin(value: boolean): Promise { await store.set(START_ON_LOGIN_KEY, value); await store.save(); } + +export async function loadShowAccountIdentity(): Promise { + const stored = await store.get(SHOW_ACCOUNT_IDENTITY_KEY); + if (typeof stored === "boolean") return stored; + return DEFAULT_SHOW_ACCOUNT_IDENTITY; +} + +export async function saveShowAccountIdentity(value: boolean): Promise { + await store.set(SHOW_ACCOUNT_IDENTITY_KEY, value); + await store.save(); +} diff --git a/src/pages/overview.tsx b/src/pages/overview.tsx index 4fd401b5..25a9e8f7 100644 --- a/src/pages/overview.tsx +++ b/src/pages/overview.tsx @@ -8,6 +8,7 @@ interface OverviewPageProps { displayMode: DisplayMode resetTimerDisplayMode: ResetTimerDisplayMode onResetTimerDisplayModeToggle?: () => void + showAccountIdentity?: boolean } export function OverviewPage({ @@ -16,6 +17,7 @@ export function OverviewPage({ displayMode, resetTimerDisplayMode, onResetTimerDisplayModeToggle, + showAccountIdentity, }: OverviewPageProps) { if (plugins.length === 0) { return ( @@ -44,6 +46,7 @@ export function OverviewPage({ displayMode={displayMode} resetTimerDisplayMode={resetTimerDisplayMode} onResetTimerDisplayModeToggle={onResetTimerDisplayModeToggle} + showAccountIdentity={showAccountIdentity} /> ))}
diff --git a/src/pages/provider-detail.tsx b/src/pages/provider-detail.tsx index 670ee504..26a8732f 100644 --- a/src/pages/provider-detail.tsx +++ b/src/pages/provider-detail.tsx @@ -8,6 +8,7 @@ interface ProviderDetailPageProps { displayMode: DisplayMode resetTimerDisplayMode: ResetTimerDisplayMode onResetTimerDisplayModeToggle?: () => void + showAccountIdentity?: boolean } export function ProviderDetailPage({ @@ -16,6 +17,7 @@ export function ProviderDetailPage({ displayMode, resetTimerDisplayMode, onResetTimerDisplayModeToggle, + showAccountIdentity, }: ProviderDetailPageProps) { if (!plugin) { return ( @@ -42,6 +44,7 @@ export function ProviderDetailPage({ displayMode={displayMode} resetTimerDisplayMode={resetTimerDisplayMode} onResetTimerDisplayModeToggle={onResetTimerDisplayModeToggle} + showAccountIdentity={showAccountIdentity} /> ) } diff --git a/src/pages/settings.test.tsx b/src/pages/settings.test.tsx index 9139f79e..0d456658 100644 --- a/src/pages/settings.test.tsx +++ b/src/pages/settings.test.tsx @@ -67,6 +67,8 @@ const defaultProps = { onGlobalShortcutChange: vi.fn(), startOnLogin: false, onStartOnLoginChange: vi.fn(), + showAccountIdentity: true, + onShowAccountIdentityChange: vi.fn(), } afterEach(() => { @@ -238,4 +240,17 @@ describe("SettingsPage", () => { await userEvent.click(screen.getByText("Start on login")) expect(onStartOnLoginChange).toHaveBeenCalledWith(true) }) + + it("toggles account identity visibility", async () => { + const onShowAccountIdentityChange = vi.fn() + render( + + ) + await userEvent.click(screen.getByText("Show account identity")) + expect(onShowAccountIdentityChange).toHaveBeenCalledWith(true) + }) }) diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index bc257202..d6bfb353 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -275,6 +275,8 @@ interface SettingsPageProps { onGlobalShortcutChange: (value: GlobalShortcut) => void; startOnLogin: boolean; onStartOnLoginChange: (value: boolean) => void; + showAccountIdentity: boolean; + onShowAccountIdentityChange: (value: boolean) => void; } export function SettingsPage({ @@ -296,6 +298,8 @@ export function SettingsPage({ onGlobalShortcutChange, startOnLogin, onStartOnLoginChange, + showAccountIdentity, + onShowAccountIdentityChange, }: SettingsPageProps) { const sensors = useSensors( useSensor(PointerSensor), @@ -489,6 +493,20 @@ export function SettingsPage({ Start on login +
+

Account Identity

+

+ Show account email beside plan badges +

+ +

Plugins

diff --git a/src/stores/app-preferences-store.ts b/src/stores/app-preferences-store.ts index 98ced539..c731cc55 100644 --- a/src/stores/app-preferences-store.ts +++ b/src/stores/app-preferences-store.ts @@ -5,6 +5,7 @@ import { DEFAULT_GLOBAL_SHORTCUT, DEFAULT_MENUBAR_ICON_STYLE, DEFAULT_RESET_TIMER_DISPLAY_MODE, + DEFAULT_SHOW_ACCOUNT_IDENTITY, DEFAULT_START_ON_LOGIN, DEFAULT_THEME_MODE, type AutoUpdateIntervalMinutes, @@ -22,6 +23,7 @@ type AppPreferencesStore = { resetTimerDisplayMode: ResetTimerDisplayMode globalShortcut: GlobalShortcut startOnLogin: boolean + showAccountIdentity: boolean menubarIconStyle: MenubarIconStyle setAutoUpdateInterval: (value: AutoUpdateIntervalMinutes) => void setThemeMode: (value: ThemeMode) => void @@ -29,6 +31,7 @@ type AppPreferencesStore = { setResetTimerDisplayMode: (value: ResetTimerDisplayMode) => void setGlobalShortcut: (value: GlobalShortcut) => void setStartOnLogin: (value: boolean) => void + setShowAccountIdentity: (value: boolean) => void setMenubarIconStyle: (value: MenubarIconStyle) => void resetState: () => void } @@ -40,6 +43,7 @@ const initialState = { resetTimerDisplayMode: DEFAULT_RESET_TIMER_DISPLAY_MODE, globalShortcut: DEFAULT_GLOBAL_SHORTCUT, startOnLogin: DEFAULT_START_ON_LOGIN, + showAccountIdentity: DEFAULT_SHOW_ACCOUNT_IDENTITY, menubarIconStyle: DEFAULT_MENUBAR_ICON_STYLE, } @@ -51,6 +55,7 @@ export const useAppPreferencesStore = create((set) => ({ setResetTimerDisplayMode: (value) => set({ resetTimerDisplayMode: value }), setGlobalShortcut: (value) => set({ globalShortcut: value }), setStartOnLogin: (value) => set({ startOnLogin: value }), + setShowAccountIdentity: (value) => set({ showAccountIdentity: value }), setMenubarIconStyle: (value) => set({ menubarIconStyle: value }), resetState: () => set(initialState), }))