feat(feature-tips): make Enable Now the full-width action beside a plain Leave Off (#28199)

Drop the 'New agent tabs still open in the terminal' line and the taller
frame it needed.
This commit is contained in:
Brennan Benson
2026-10-12 23:28:50 -07:00
committed by GitHub
parent cbeb537146
commit 484a54081a
10 changed files with 26 additions and 43 deletions
@@ -29,6 +29,7 @@ export function FeatureTipActions({
onPrimaryAction: () => void
onSkip: () => void
showSkip?: boolean
/** Fills the footer row, or what a sibling button leaves of it. */
fullWidth?: boolean
primaryButtonRef?: Ref<HTMLButtonElement>
/** Overrides the tip's CTA for tips whose button changes with their state. */
@@ -43,7 +44,7 @@ export function FeatureTipActions({
) : null}
<Button
ref={primaryButtonRef}
className={fullWidth ? 'w-full' : undefined}
className={fullWidth ? 'w-full flex-1' : undefined}
onClick={onPrimaryAction}
disabled={primaryBusy}
>
@@ -1,7 +1,6 @@
import type { ComponentProps, JSX, ReactNode } from 'react'
import { Badge } from '@/components/ui/badge'
import { Dialog, DialogContent } from '@/components/ui/dialog'
import { cn } from '@/lib/utils'
/** Shared two-column tip layout: copy and actions on the left, a feature visual on the right. */
export function FeatureTipDialogFrame({
@@ -9,25 +8,19 @@ export function FeatureTipDialogFrame({
onOpenChange,
onOpenAutoFocus,
visual,
tall = false,
children
}: {
open: boolean
onOpenChange: (open: boolean) => void
onOpenAutoFocus: ComponentProps<typeof DialogContent>['onOpenAutoFocus']
visual: ReactNode
/** Room for an extra row of copy without scrolling. */
tall?: boolean
children: ReactNode
}): JSX.Element {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
surface="feature-tip"
className={cn(
'!flex max-h-[calc(100vh-2rem)] flex-col overflow-hidden sm:max-w-4xl md:!flex-row',
tall ? 'md:!h-[min(33rem,calc(100vh-2rem))]' : 'md:!h-[min(27rem,calc(100vh-2rem))]'
)}
className="!flex max-h-[calc(100vh-2rem)] flex-col overflow-hidden sm:max-w-4xl md:!h-[min(27rem,calc(100vh-2rem))] md:!flex-row"
showCloseButton
onOpenAutoFocus={onOpenAutoFocus}
>
@@ -84,30 +84,26 @@ describe('NativeChatUpgradeTipDialog', () => {
it('shows only Got It when chat mode is already on', () => {
const html = renderDialog(true)
const text = textOf(html)
expect(text).not.toContain('New agent tabs still open in the terminal')
expect(text).not.toContain('Turn on chat mode')
expect(text).not.toContain('Enable Now')
expect(text).not.toContain('Okay')
expect(text).not.toContain('Leave Off')
expect(html).not.toContain('role="switch"')
expect(html).toContain('27rem')
expect(html).not.toContain('33rem')
})
it('offers Okay and Enable Now when chat mode is off, without a switch', () => {
it('offers Leave Off and Enable Now when chat mode is off, without a switch', () => {
const html = renderDialog(false)
const text = textOf(html)
expect(text).toContain('New agent tabs still open in the terminal, as before.')
expect(text).toContain('Okay')
expect(text).toContain('Leave Off')
expect(text).toContain('Enable Now')
expect(text).not.toContain('Got It')
expect(text).not.toContain('Turn on chat mode')
expect(html).not.toContain('role="switch"')
expect(html).toContain('33rem')
expect(html).toContain('27rem')
// The way back to the terminal stays in the tip.
expect(text).toContain('Resume in New CLI copies a Claude or Codex chat')
})
it('dismisses with Okay and runs the primary action only with Enable Now', () => {
it('dismisses with Leave Off and runs the primary action only with Enable Now', () => {
const onPrimaryAction = vi.fn()
const onOpenChange = vi.fn()
const container = document.createElement('div')
@@ -115,10 +111,14 @@ describe('NativeChatUpgradeTipDialog', () => {
act(() => root.render(dialog(false, onPrimaryAction, onOpenChange)))
const buttons = container.querySelectorAll<HTMLButtonElement>('footer button')
expect(buttons).toHaveLength(2)
expect(buttons[0]?.textContent).toBe('Okay')
expect(buttons[0]?.getAttribute('data-variant')).toBe('ghost')
expect(buttons[0]?.textContent).toBe('Leave Off')
// Leave Off is plain text, not a Button primitive.
expect(buttons[0]?.hasAttribute('data-variant')).toBe(false)
expect(buttons[1]?.textContent).toBe('Enable Now')
expect(buttons[1]?.getAttribute('data-variant')).toBe('default')
// Enable Now takes the row width Leave Off leaves.
expect(buttons[1]?.classList.contains('flex-1')).toBe(true)
expect(buttons[0]?.classList.contains('flex-1')).toBe(false)
act(() => buttons[0]?.click())
expect(onOpenChange).toHaveBeenCalledWith(false)
expect(onPrimaryAction).not.toHaveBeenCalled()
@@ -134,7 +134,6 @@ describe('NativeChatUpgradeTipDialog', () => {
expect(container.querySelectorAll('footer button')).toHaveLength(1)
expect(container.querySelector('footer button')?.textContent).toBe('Got It')
expect(container.querySelector('[role="switch"]')).toBeNull()
expect(container.textContent).not.toContain('New agent tabs still open in the terminal')
act(() => root.unmount())
})
@@ -2,7 +2,6 @@ import { useRef, type JSX } from 'react'
import { MessagesSquare, SquareTerminal, type LucideIcon } from 'lucide-react'
import type { FeatureTip } from '../../../../shared/feature-tips'
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { translate } from '@/i18n/i18n'
import { FeatureTipActions } from './FeatureTipActions'
import {
@@ -83,7 +82,6 @@ export function NativeChatUpgradeTipDialog({
primaryButtonRef.current?.focus({ preventScroll: true })
}}
visual={<NativeChatUpgradeFeatureTipVisual />}
tall={offerChatMode}
>
<DialogHeader className="text-left">
<div>
@@ -91,14 +89,6 @@ export function NativeChatUpgradeTipDialog({
<DialogTitle size="display">
{translate('featureTips.nativeChatUpgrade.title', 'Native chat got an upgrade')}
</DialogTitle>
{offerChatMode ? (
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
{translate(
'featureTips.nativeChatUpgrade.chatModeNote',
'New agent tabs still open in the terminal, as before.'
)}
</p>
) : null}
<DialogDescription className="mt-3 max-w-2xl">
<span className="block space-y-3 leading-relaxed">
<span className="block">
@@ -156,11 +146,17 @@ export function NativeChatUpgradeTipDialog({
</div>
</DialogHeader>
<DialogFooter className="mt-8 flex sm:justify-end">
<DialogFooter className="mt-8 flex flex-row items-center">
{offerChatMode ? (
<Button variant="ghost" onClick={() => onOpenChange(false)} disabled={primaryBusy}>
{translate('featureTips.nativeChatUpgrade.okay', 'Okay')}
</Button>
// Why: plain text, flush with the copy, so Enable Now reads as the only button.
<button
type="button"
onClick={() => onOpenChange(false)}
disabled={primaryBusy}
className="mr-4 shrink-0 cursor-pointer rounded-sm text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
>
{translate('featureTips.nativeChatUpgrade.leaveOff', 'Leave Off')}
</button>
) : null}
<FeatureTipActions
currentTip={tip}
@@ -168,7 +164,7 @@ export function NativeChatUpgradeTipDialog({
onPrimaryAction={onPrimaryAction}
onSkip={() => onOpenChange(false)}
showSkip={false}
fullWidth={!offerChatMode}
fullWidth
primaryButtonRef={primaryButtonRef}
label={
offerChatMode
+1 -2
View File
@@ -19232,8 +19232,7 @@
"nativeChatUpgrade": {
"eyebrow": "New",
"title": "Native chat got an upgrade",
"chatModeNote": "New agent tabs still open in the terminal, as before.",
"okay": "Okay",
"leaveOff": "Leave Off",
"enableNow": "Enable Now",
"enableError": "Could not enable chat mode.",
"intro": "New chats with supported agents now open in the upgraded chat view. To move between chat and CLI, open {{panel}} in the right sidebar:",
-1
View File
@@ -19073,7 +19073,6 @@
"nativeChatUpgrade": {
"eyebrow": "Nuevo",
"title": "El chat nativo mejora",
"chatModeNote": "Las pestañas de agente nuevas siguen abriéndose en la terminal, como antes.",
"intro": "Los chats nuevos de agentes compatibles usan la vista de chat mejorada. Para alternar entre chat y CLI, abre {{panel}} (barra lateral derecha):",
"resumeInNativeChat": "{{action}} abre una conversación de CLI compatible en el chat nativo.",
"resumeInCli": "{{action}} copia un chat de Claude/Codex a una CLI nueva sin tocar el original.",
-1
View File
@@ -19050,7 +19050,6 @@
"nativeChatUpgrade": {
"eyebrow": "Nouveau",
"title": "Le chat natif évolue",
"chatModeNote": "Les nouveaux onglets d'agent s'ouvrent toujours dans le terminal, comme avant.",
"intro": "Les nouveaux chats des agents compatibles utilisent la vue améliorée. Pour alterner entre chat et CLI, ouvrez {{panel}} (barre latérale droite) :",
"resumeInNativeChat": "{{action}} ouvre une conversation CLI compatible dans le chat natif.",
"resumeInCli": "{{action}} copie un chat Claude/Codex dans une CLI neuve ; l'original reste intact.",
-1
View File
@@ -19101,7 +19101,6 @@
"nativeChatUpgrade": {
"eyebrow": "新機能",
"title": "ネイティブチャットを刷新",
"chatModeNote": "新しい Agent タブは、これまでどおりターミナルで開きます。",
"intro": "対応 Agent の新しいチャットはアップグレード版のチャットビューで開きます。チャットと CLI の切り替えは右サイドバーの{{panel}}から:",
"resumeInNativeChat": "{{action}}: 対応する CLI の会話をネイティブチャットで開きます。",
"resumeInCli": "{{action}}: Claude/Codex のチャットを新しい CLI セッションに複製します。元のチャットは残ります。",
-1
View File
@@ -19131,7 +19131,6 @@
"nativeChatUpgrade": {
"eyebrow": "새 기능",
"title": "기본 채팅이 업그레이드되었습니다",
"chatModeNote": "새 Agent 탭은 이전처럼 터미널에서 열립니다.",
"intro": "이제 지원되는 Agent의 새 채팅은 업그레이드된 채팅 보기에서 열립니다. 채팅과 CLI 사이를 오가려면 오른쪽 사이드바에서 {{panel}}을 여세요:",
"resumeInNativeChat": "{{action}}: 지원되는 CLI 대화를 기본 채팅에서 엽니다.",
"resumeInCli": "{{action}}: Claude 또는 Codex 채팅을 새 CLI 세션으로 복사합니다. 원래 채팅은 그대로 유지됩니다.",
-1
View File
@@ -19115,7 +19115,6 @@
"nativeChatUpgrade": {
"eyebrow": "新功能",
"title": "本机聊天已升级",
"chatModeNote": "新的智能体标签页仍像以前一样在终端中打开。",
"intro": "受支持智能体的新聊天现在会在升级后的聊天视图中打开。要在聊天和 CLI 之间切换,请打开右侧边栏中的{{panel}}:",
"resumeInNativeChat": "{{action}}:在本机聊天中打开受支持的 CLI 对话。",
"resumeInCli": "{{action}}:将 Claude 或 Codex 聊天复制到新的 CLI 会话中。原始聊天保持不变。",