feat(codex): remove "Not now" from account-switch notice (#9613)

* feat(codex): drop "Not now" collapse from account-switch notice

The Codex account-switch overlay had a "Not now" button that collapsed
the loud dialog into a corner chip via component-local state. Remove the
button and all of its collapse state: the useState/useEffect, the
CollapsedRestartChip, and the now-dead collapse helpers.

buildCodexRestartNoticeKey still backs the focus effect, so the module is
renamed codex-restart-overlay-collapse.ts -> codex-restart-notice-key.ts
and its tests trimmed to match. The orphaned "Not now" i18n key is pruned
from all locale catalogs.

* test(codex): lock restart notice actions
This commit is contained in:
Brennan Benson
2026-07-20 15:44:03 -07:00
committed by GitHub
parent e218dfbf8b
commit d19cb8bc47
11 changed files with 30 additions and 175 deletions
@@ -1,4 +1,4 @@
import { useEffect, useId, useMemo, useRef, useState } from 'react'
import { useEffect, useId, useMemo, useRef } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { RefreshCw } from 'lucide-react'
import { Button } from '@/components/ui/button'
@@ -6,11 +6,7 @@ import { useAppStore } from '../store'
import { selectCodexRestartInputs } from './codex-restart-chip-inputs'
import { translate } from '@/i18n/i18n'
import { shouldFocusMobileDriverAction } from './terminal-pane/mobile-driver-overlay-focus'
import {
buildCodexRestartNoticeKey,
createCodexRestartOverlayCollapseState,
getCodexRestartOverlayCollapseState
} from './codex-restart-overlay-collapse'
import { buildCodexRestartNoticeKey } from './codex-restart-notice-key'
const EMPTY_TABS: { id: string }[] = []
@@ -99,16 +95,6 @@ export default function CodexRestartChip({
const clearCodexRestartNotice = useAppStore((s) => s.clearCodexRestartNotice)
const noticeKey = restartNotice ? buildCodexRestartNoticeKey(restartNotice) : null
const [collapseState, setCollapseState] = useState(() =>
createCodexRestartOverlayCollapseState(noticeKey)
)
const currentCollapseState = getCodexRestartOverlayCollapseState(collapseState, noticeKey)
useEffect(() => {
// Why: a new account switch must reopen loud mode without updating state
// during render, which can trip React's external-store snapshot guard.
setCollapseState((state) => getCodexRestartOverlayCollapseState(state, noticeKey))
}, [noticeKey])
if (staleWorktreePtyIds.length === 0 || !restartNotice) {
return null
@@ -122,22 +108,11 @@ export default function CodexRestartChip({
dismissStaleWorktreePtyIds(staleWorktreePtyIds, clearCodexRestartNotice)
}
if (currentCollapseState.collapsed) {
return (
<CollapsedRestartChip
restartNotice={restartNotice}
onExpand={() => setCollapseState(createCodexRestartOverlayCollapseState(noticeKey))}
onRestart={handleRestart}
/>
)
}
return (
<LoudRestartOverlay
isVisible={isVisible}
noticeKey={noticeKey}
restartNotice={restartNotice}
onCollapse={() => setCollapseState({ noticeKey, collapsed: true })}
onDismiss={handleDismiss}
onRestart={handleRestart}
/>
@@ -148,14 +123,12 @@ function LoudRestartOverlay({
isVisible,
noticeKey,
restartNotice,
onCollapse,
onDismiss,
onRestart
}: {
isVisible: boolean
noticeKey: string | null
restartNotice: RestartNotice
onCollapse: () => void
onDismiss: () => void
onRestart: () => void
}): React.JSX.Element {
@@ -218,9 +191,6 @@ function LoudRestartOverlay({
<Button type="button" variant="outline" size="sm" onClick={onDismiss}>
{translate('auto.components.CodexRestartChip.6133594b12', 'Keep old account')}
</Button>
<Button type="button" variant="outline" size="sm" onClick={onCollapse}>
{translate('auto.components.CodexRestartChip.b9000fe511', 'Not now')}
</Button>
<Button ref={restartRef} type="button" variant="default" size="sm" onClick={onRestart}>
<RefreshCw />
{translate('auto.components.CodexRestartChip.c72a5fb234', 'Restart')}
@@ -230,41 +200,3 @@ function LoudRestartOverlay({
</div>
)
}
function CollapsedRestartChip({
restartNotice,
onExpand,
onRestart
}: {
restartNotice: RestartNotice
onExpand: () => void
onRestart: () => void
}): React.JSX.Element {
return (
<div
className="absolute right-2 top-2 z-50 flex max-w-[min(100%-1rem,24rem)] items-center gap-1.5 rounded-full border border-border bg-card px-2 py-1 text-xs font-medium text-card-foreground shadow-xs"
role="status"
aria-live="polite"
>
<RefreshCw className="size-3 shrink-0 text-foreground" aria-hidden="true" />
<Button
type="button"
variant="ghost"
size="xs"
className="min-w-0 max-w-[10rem] px-1 font-medium"
onClick={onExpand}
>
<span className="truncate">
{translate(
'auto.components.CodexRestartChip.a4c8e1b2f7',
'Codex is still signed in as {{value0}}',
{ value0: restartNotice.previousAccountLabel }
)}
</span>
</Button>
<Button type="button" variant="default" size="xs" onClick={onRestart}>
{translate('auto.components.CodexRestartChip.c72a5fb234', 'Restart')}
</Button>
</div>
)
}
@@ -92,7 +92,7 @@ describe('CodexRestartChip helpers', () => {
expect(clearCodexRestartNotice).toHaveBeenCalledTimes(2)
})
it('renders a stale Codex restart notice without an external-store update loop', async () => {
it('renders only account-resolution actions without an external-store update loop', async () => {
useAppStore.setState({
tabsByWorktree: {
'worktree-1': [
@@ -124,5 +124,8 @@ describe('CodexRestartChip helpers', () => {
})
expect(container.textContent).toContain('Codex is still signed in as old@example.com')
expect(
Array.from(container.querySelectorAll('button'), (button) => button.textContent?.trim())
).toEqual(['Keep old account', 'Restart'])
})
})
@@ -0,0 +1,13 @@
import { describe, expect, it } from 'vitest'
import { buildCodexRestartNoticeKey } from './codex-restart-notice-key'
describe('codex restart notice key', () => {
it('builds a stable notice key from account labels', () => {
expect(
buildCodexRestartNoticeKey({
previousAccountLabel: 'Account A',
nextAccountLabel: 'Account B'
})
).toBe('Account A\u0000Account B')
})
})
@@ -0,0 +1,6 @@
export function buildCodexRestartNoticeKey(args: {
previousAccountLabel: string
nextAccountLabel: string
}): string {
return `${args.previousAccountLabel}\u0000${args.nextAccountLabel}`
}
@@ -1,62 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
buildCodexRestartNoticeKey,
createCodexRestartOverlayCollapseState,
getCodexRestartOverlayCollapseState
} from './codex-restart-overlay-collapse'
describe('codex restart overlay collapse', () => {
it('builds a stable notice key from account labels', () => {
expect(
buildCodexRestartNoticeKey({
previousAccountLabel: 'Account A',
nextAccountLabel: 'Account B'
})
).toBe('Account A\u0000Account B')
})
it('reopens loud mode when the notice key changes', () => {
const collapsed = {
noticeKey: buildCodexRestartNoticeKey({
previousAccountLabel: 'Account A',
nextAccountLabel: 'Account B'
}),
collapsed: true
}
expect(
getCodexRestartOverlayCollapseState(
collapsed,
buildCodexRestartNoticeKey({
previousAccountLabel: 'Account A',
nextAccountLabel: 'Account C'
})
)
).toEqual({
noticeKey: 'Account A\u0000Account C',
collapsed: false
})
})
it('preserves collapse state for the same notice key', () => {
const noticeKey = buildCodexRestartNoticeKey({
previousAccountLabel: 'Account A',
nextAccountLabel: 'Account B'
})
const collapsed = { noticeKey, collapsed: true }
expect(getCodexRestartOverlayCollapseState(collapsed, noticeKey)).toBe(collapsed)
})
it('creates loud mode for a new notice key', () => {
const noticeKey = buildCodexRestartNoticeKey({
previousAccountLabel: 'Account A',
nextAccountLabel: 'Account B'
})
expect(createCodexRestartOverlayCollapseState(noticeKey)).toEqual({
noticeKey,
collapsed: false
})
})
})
@@ -1,32 +0,0 @@
export type CodexRestartOverlayCollapseState = {
noticeKey: string | null
collapsed: boolean
}
export function buildCodexRestartNoticeKey(args: {
previousAccountLabel: string
nextAccountLabel: string
}): string {
return `${args.previousAccountLabel}\u0000${args.nextAccountLabel}`
}
export function getCodexRestartOverlayCollapseState(
state: CodexRestartOverlayCollapseState,
noticeKey: string | null
): CodexRestartOverlayCollapseState {
return state.noticeKey === noticeKey
? state
: {
noticeKey,
collapsed: false
}
}
export function createCodexRestartOverlayCollapseState(
noticeKey: string | null
): CodexRestartOverlayCollapseState {
return {
noticeKey,
collapsed: false
}
}
+1 -2
View File
@@ -769,8 +769,7 @@
"9263e75f49": "Codex is using the previous account",
"d3e8a1f4b2": "Account switched",
"9375620cc3": "Restart this session to use {{value0}}. It stays on the previous account until you do.",
"6133594b12": "Keep old account",
"b9000fe511": "Not now"
"6133594b12": "Keep old account"
},
"FirstLaunchBanner": {
"b9e1b966c7": "Dismiss notice",
+1 -2
View File
@@ -746,8 +746,7 @@
"a4c8e1b2f7": "Codex sigue conectado como {{value0}}",
"d3e8a1f4b2": "Cuenta cambiada",
"9375620cc3": "Restart this session to use {{value0}}. It stays on the previous account until you do.",
"6133594b12": "Keep old account",
"b9000fe511": "Not now"
"6133594b12": "Keep old account"
},
"FirstLaunchBanner": {
"b9e1b966c7": "Descartar aviso",
+1 -2
View File
@@ -746,8 +746,7 @@
"a4c8e1b2f7": "Codex はまだ {{value0}} としてサインインしています",
"d3e8a1f4b2": "アカウントを切り替えました",
"9375620cc3": "Restart this session to use {{value0}}. It stays on the previous account until you do.",
"6133594b12": "Keep old account",
"b9000fe511": "Not now"
"6133594b12": "Keep old account"
},
"FirstLaunchBanner": {
"b9e1b966c7": "通知を閉じる",
+1 -2
View File
@@ -746,8 +746,7 @@
"a4c8e1b2f7": "Codex가 아직 {{value0}} 계정으로 로그인되어 있습니다",
"d3e8a1f4b2": "계정 전환됨",
"9375620cc3": "Restart this session to use {{value0}}. It stays on the previous account until you do.",
"6133594b12": "Keep old account",
"b9000fe511": "Not now"
"6133594b12": "Keep old account"
},
"FirstLaunchBanner": {
"b9e1b966c7": "알림 닫기",
+1 -2
View File
@@ -746,8 +746,7 @@
"a4c8e1b2f7": "Codex 仍以 {{value0}} 身份登录",
"d3e8a1f4b2": "账户已切换",
"9375620cc3": "Restart this session to use {{value0}}. It stays on the previous account until you do.",
"6133594b12": "Keep old account",
"b9000fe511": "Not now"
"6133594b12": "Keep old account"
},
"FirstLaunchBanner": {
"b9e1b966c7": "关闭通知",