fix: clarify CLIProxy setup actions

Make account setup the primary CLIProxy CTA, keep advanced variant creation secondary, and contain setup dialogs across narrow viewports.
This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-12 19:23:59 -04:00
committed by GitHub
parent 26311f7752
commit ac333dd1d5
8 changed files with 258 additions and 72 deletions
@@ -378,13 +378,13 @@ export function AddAccountDialog({
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent
className="sm:max-w-md"
className="grid max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-md grid-rows-[auto_minmax(0,1fr)] overflow-hidden p-0 sm:max-w-md"
onInteractOutside={(e) => {
// Prevent accidental close by clicking outside during auth
if (showAuthUI) e.preventDefault();
}}
>
<DialogHeader>
<DialogHeader className="min-w-0 px-6 pt-6 pr-12">
<DialogTitle>{t('addAccountDialog.title', { displayName })}</DialogTitle>
<DialogDescription>
{isKiro
@@ -395,7 +395,7 @@ export function AddAccountDialog({
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="min-h-0 space-y-4 overflow-y-auto px-6 py-4">
{requiresAgyResponsibilityFlow && !showAuthUI && (
<AntigravityResponsibilityChecklist
value={agyRiskChecklist}
@@ -748,12 +748,17 @@ export function AddAccountDialog({
)}
{/* Action buttons */}
<div className="flex items-center justify-end gap-2 pt-2">
<Button variant="ghost" onClick={handleCancel}>
<div className="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:items-center sm:justify-end">
<Button variant="ghost" onClick={handleCancel} className="w-full sm:w-auto">
{t('addAccountDialog.cancel')}
</Button>
{isKiro && !showAuthUI && (
<Button variant="outline" onClick={handleKiroImport} disabled={isPending}>
<Button
variant="outline"
onClick={handleKiroImport}
disabled={isPending}
className="w-full sm:w-auto"
>
{kiroImportMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
@@ -770,6 +775,7 @@ export function AddAccountDialog({
{!showAuthUI && (
<Button
onClick={handleAuthenticate}
className="w-full sm:w-auto"
disabled={
isPending ||
isPowerUserModePending ||
@@ -142,8 +142,7 @@ export function AccountsSection({
/>
)}
<User className="w-4 h-4" />
{/* TODO i18n: missing key for "Accounts" */}
Accounts
{t('providerEditor.accounts')}
{accounts.length > 0 && (
<Badge variant="secondary" className="text-xs">
{accounts.length}
@@ -152,7 +151,7 @@ export function AccountsSection({
</h3>
<Button variant="default" size="sm" className="h-7 text-xs gap-1" onClick={onAddAccount}>
<Plus className="w-3 h-3" />
Add
{t('providerEditor.addAccount')}
</Button>
</div>
+7 -5
View File
@@ -177,7 +177,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent
className="sm:max-w-lg"
className="grid max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-lg grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden p-0 sm:max-w-lg"
onPointerDownOutside={(e) => {
if (step !== 'success' && step !== 'provider') {
e.preventDefault();
@@ -189,8 +189,8 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
}
}}
>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<DialogHeader className="min-w-0 px-6 pt-6 pr-12">
<DialogTitle className="flex min-w-0 items-center gap-2">
<Sparkles className="w-5 h-5 text-primary" />
{i18n.t('setupWizard.title')}
</DialogTitle>
@@ -203,7 +203,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="min-h-0 space-y-4 overflow-y-auto px-6 py-4">
{step === 'provider' && (
<ProviderStep providers={PROVIDERS} onSelect={handleProviderSelect} />
)}
@@ -254,7 +254,9 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
{step === 'success' && <SuccessStep variantName={variantName} onClose={onClose} />}
</div>
<ProgressIndicator currentProgress={currentProgress} allSteps={ALL_STEPS} />
<div className="px-6 pb-6">
<ProgressIndicator currentProgress={currentProgress} allSteps={ALL_STEPS} />
</div>
</DialogContent>
</Dialog>
);
@@ -73,12 +73,12 @@ export function VariantStep({
return (
<div className="space-y-4">
{selectedAccount && (
<div className="flex items-start gap-2 p-2 bg-muted/50 rounded-md text-sm">
<User className="w-4 h-4" />
<div className="space-y-1">
<div className="flex min-w-0 items-start gap-2 rounded-md bg-muted/50 p-2 text-sm">
<User className="w-4 h-4 shrink-0" />
<div className="min-w-0 space-y-1">
<span>
{t('setupVariant.using')}{' '}
<span className={cn(privacyMode && PRIVACY_BLUR_CLASS)}>
<span className={cn('break-all', privacyMode && PRIVACY_BLUR_CLASS)}>
{selectedAccountIdentity?.email}
</span>
</span>
@@ -167,16 +167,18 @@ export function VariantStep({
value={catalogModels.some((m) => m.id === modelName) ? modelName : ''}
onValueChange={handleModelSelect}
>
<SelectTrigger>
<SelectTrigger className="min-w-0">
<SelectValue placeholder={t('setupVariant.selectModel')} />
</SelectTrigger>
<SelectContent>
<SelectContent className="max-w-[calc(100vw-2rem)]">
{catalogModels.map((m) => (
<SelectItem key={m.id} value={m.id}>
<div className="flex items-center gap-2">
<span>{m.name}</span>
<div className="flex min-w-0 items-center gap-2">
<span className="truncate">{m.name}</span>
{m.description && (
<span className="text-xs text-muted-foreground">- {m.description}</span>
<span className="truncate text-xs text-muted-foreground">
- {m.description}
</span>
)}
</div>
</SelectItem>
@@ -196,12 +198,12 @@ export function VariantStep({
</div>
</div>
<div className="flex items-center justify-between pt-2">
<Button variant="ghost" onClick={onBack}>
<div className="flex flex-col gap-2 pt-2 sm:flex-row sm:items-center sm:justify-between">
<Button variant="ghost" onClick={onBack} className="justify-start">
<ArrowLeft className="w-4 h-4 mr-2" />
{t('setupVariant.back')}
</Button>
<div className="flex items-center gap-2">
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:items-center">
<Button variant="ghost" onClick={onSkip}>
{t('setupVariant.skip')}
</Button>
+47 -24
View File
@@ -140,9 +140,9 @@ const resources = {
},
setupVariant: {
using: 'Using:',
variantNameRequired: 'Variant Name *',
variantNameRequired: 'Runtime Variant Name *',
variantNamePlaceholder: 'e.g., my-gemini, g3, flash',
invokeHintPrefix: 'Use this name to invoke:',
invokeHintPrefix: 'Run with this variant:',
model: 'Model',
modelPlaceholder: 'e.g., gemini-2.5-pro, claude-opus-4-6-thinking',
choosePresetInstead: 'Choose from presets instead',
@@ -155,7 +155,7 @@ const resources = {
skip: 'Skip',
creating: 'Creating...',
createVariant: 'Create Variant',
skipHint: 'Skip if you just wanted to add an account without creating a variant',
skipHint: 'Skip if account setup is enough and you do not need a separate runtime variant',
},
customPresetDialog: {
title: 'Custom Preset',
@@ -1277,6 +1277,10 @@ const resources = {
emptyControlPanelPrefix: 'For live usage stats and real-time monitoring, visit the',
controlPanel: 'Control Panel',
quickSetup: 'Quick Setup',
addAccount: 'Add Account',
advancedVariant: 'Advanced Variant',
setupActionsHint:
'Add accounts here. Create variants only when you need a separate runtime/profile.',
accountManagement: 'CCS-level account management',
providers: 'Providers',
variants: 'Variants',
@@ -1783,11 +1787,11 @@ const resources = {
// Domain 2: Accounts / Auth
// ========================================
setupWizard: {
title: 'Quick Setup Wizard',
stepProviderDesc: 'Select a provider to get started',
title: 'Advanced Variant Setup',
stepProviderDesc: 'Select a provider for the runtime variant',
stepAuthDesc: 'Authenticate with your provider',
stepAccountDesc: 'Select which account to use',
stepVariantDesc: 'Create your custom variant',
stepVariantDesc: 'Optional: create a custom runtime variant',
stepSuccessDesc: 'Setup complete!',
authStep: {
authenticateWith: 'Authenticate with {{provider}} to add an account',
@@ -1948,6 +1952,8 @@ const resources = {
status: 'Status',
loadingSettings: 'Loading settings...',
loadingEditor: 'Loading editor...',
accounts: 'Accounts',
addAccount: 'Add Account',
noAccountsConnected: 'No accounts connected',
addAccountToStart: 'Add an account to get started',
gcpProjectIdReadonly: 'GCP Project ID (read-only)',
@@ -2796,9 +2802,9 @@ const resources = {
},
setupVariant: {
using: '当前使用:',
variantNameRequired: '变体名称 *',
variantNameRequired: '运行时变体名称 *',
variantNamePlaceholder: '例如:my-gemini、g3、flash',
invokeHintPrefix: '可通过该名称调用',
invokeHintPrefix: '使用此变体运行',
model: '模型',
modelPlaceholder: '例如:gemini-2.5-pro、claude-opus-4-6-thinking',
choosePresetInstead: '改为从预设中选择',
@@ -2811,7 +2817,7 @@ const resources = {
skip: '跳过',
creating: '创建中...',
createVariant: '创建变体',
skipHint: '如果你只想添加账号而不创建变体,可以直接跳过',
skipHint: '如果账号设置已足够且不需要单独运行时变体,可以跳过',
},
customPresetDialog: {
title: '自定义预设',
@@ -3855,6 +3861,9 @@ const resources = {
emptyControlPanelPrefix: '如需查看实时用量和监控,请前往',
controlPanel: '控制面板',
quickSetup: '快速设置',
addAccount: '添加账号',
advancedVariant: '高级变体',
setupActionsHint: '在这里添加账号。只有需要单独运行时/配置档时才创建变体。',
accountManagement: 'CCS 级账号管理',
providers: '提供商',
variants: '变体',
@@ -4327,11 +4336,11 @@ const resources = {
failedSave: '保存失败',
},
setupWizard: {
title: '快速设置向导',
stepProviderDesc: '选择一个提供商开始',
title: '高级变体设置',
stepProviderDesc: '为运行时变体选择提供商',
stepAuthDesc: '完成提供商认证',
stepAccountDesc: '选择要使用的账号',
stepVariantDesc: '创建自定义变体',
stepVariantDesc: '可选:创建自定义运行时变体',
stepSuccessDesc: '设置完成!',
authStep: {
authenticateWith: '通过 {{provider}} 认证以添加账号',
@@ -4488,6 +4497,8 @@ const resources = {
status: '状态',
loadingSettings: '加载设置中...',
loadingEditor: '加载编辑器中...',
accounts: '账号',
addAccount: '添加账号',
noAccountsConnected: '未连接账号',
addAccountToStart: '添加账号以开始',
gcpProjectIdReadonly: 'GCP 项目 ID(只读)',
@@ -5314,9 +5325,9 @@ const resources = {
},
setupVariant: {
using: 'Sử dụng:',
variantNameRequired: 'Tên biến thể *',
variantNameRequired: 'Tên biến thể runtime *',
variantNamePlaceholder: 'ví dụ: my-gemini, g3, flash',
invokeHintPrefix: 'Sử dụng tên này để gọi:',
invokeHintPrefix: 'Chạy bằng biến thể này:',
model: 'Mô hình',
modelPlaceholder: 'ví dụ: gemini-2.5-pro, claude-opus-4-5',
choosePresetInstead: 'Hoặc chọn từ preset có sẵn',
@@ -5329,7 +5340,7 @@ const resources = {
skip: 'Bỏ qua',
creating: 'Đang tạo...',
createVariant: 'Tạo biến thể',
skipHint: 'Bỏ qua nếu bạn chỉ muốn thêm tài khoản mà không tạo biến thể',
skipHint: 'Bỏ qua nếu thiết lập tài khoản là đủ và không cần biến thể runtime riêng',
},
customPresetDialog: {
title: 'Preset tùy chỉnh',
@@ -6460,6 +6471,9 @@ const resources = {
'Để biết số liệu thống kê sử dụng trực tiếp và theo dõi thời gian thực, hãy truy cập',
controlPanel: 'Bảng điều khiển',
quickSetup: 'Thiết lập nhanh',
addAccount: 'Thêm tài khoản',
advancedVariant: 'Biến thể nâng cao',
setupActionsHint: 'Thêm tài khoản tại đây. Chỉ tạo biến thể khi cần runtime/profile riêng.',
accountManagement: 'Quản lý tài khoản cấp CCS',
providers: 'Nhà cung cấp',
variants: 'Biến thể',
@@ -6940,11 +6954,11 @@ const resources = {
failedSave: 'Không lưu được',
},
setupWizard: {
title: 'Trình thiết lập nhanh',
stepProviderDesc: 'Chọn nhà cung cấp để bắt đầu',
title: 'Thiết lập biến thể nâng cao',
stepProviderDesc: 'Chọn nhà cung cấp cho biến thể runtime',
stepAuthDesc: 'Xác thực với nhà cung cấp',
stepAccountDesc: 'Chọn tài khoản để sử dụng',
stepVariantDesc: 'Tạo biến thể tùy chỉnh',
stepVariantDesc: 'Tùy chọn: tạo biến thể runtime tùy chỉnh',
stepSuccessDesc: 'Thiết lập hoàn tất!',
authStep: {
authenticateWith: 'Xác thực với {{provider}} để thêm tài khoản',
@@ -7101,6 +7115,8 @@ const resources = {
status: 'Trạng thái',
loadingSettings: 'Đang tải cài đặt...',
loadingEditor: 'Đang tải trình soạn thảo...',
accounts: 'Tài khoản',
addAccount: 'Thêm tài khoản',
noAccountsConnected: 'Chưa kết nối tài khoản nào',
addAccountToStart: 'Thêm tài khoản để bắt đầu',
gcpProjectIdReadonly: 'GCP Project ID (chỉ đọc)',
@@ -7941,9 +7957,9 @@ const resources = {
},
setupVariant: {
using: '使用中:',
variantNameRequired: 'バリアント名 *',
variantNameRequired: 'ランタイムバリアント名 *',
variantNamePlaceholder: '例: my-gemini, g3, flash',
invokeHintPrefix: 'この名前で呼び出せます:',
invokeHintPrefix: 'このバリアントで実行:',
model: 'モデル',
modelPlaceholder: '例: gemini-2.5-pro, claude-opus-4-6-thinking',
choosePresetInstead: '代わりにプリセットから選択',
@@ -7956,7 +7972,8 @@ const resources = {
skip: 'スキップ',
creating: '作成中...',
createVariant: 'バリアントを作成',
skipHint: 'アカウント追加だけが目的なら、バリアント作成はスキップできます',
skipHint:
'アカウント設定だけで十分で、別ランタイムバリアントが不要な場合はスキップできます',
},
customPresetDialog: {
title: 'カスタムプリセット',
@@ -9092,6 +9109,10 @@ const resources = {
emptyControlPanelPrefix: 'ライブ利用統計とリアルタイム監視は',
controlPanel: 'コントロールパネルで確認できます',
quickSetup: 'クイックセットアップ',
addAccount: 'アカウントを追加',
advancedVariant: '高度なバリアント',
setupActionsHint:
'ここでアカウントを追加します。別ランタイム/プロファイルが必要な場合のみバリアントを作成します。',
accountManagement: 'CCSレベルのアカウント管理',
providers: 'プロバイダー',
variants: 'バリアント',
@@ -10008,6 +10029,8 @@ const resources = {
status: 'ステータス',
loadingSettings: '設定を読み込み中...',
loadingEditor: 'エディターを読み込み中...',
accounts: 'アカウント',
addAccount: 'アカウントを追加',
noAccountsConnected: '接続されたアカウントなし',
addAccountToStart: 'アカウントを追加して開始',
gcpProjectIdReadonly: 'GCP プロジェクト ID(読み取り専用)',
@@ -10276,11 +10299,11 @@ const resources = {
},
},
setupWizard: {
title: 'クイックセットアップウィザード',
stepProviderDesc: 'プロバイダーを選択して開始',
title: '高度なバリアント設定',
stepProviderDesc: 'ランタイムバリアント用のプロバイダーを選択',
stepAuthDesc: 'プロバイダーで認証',
stepAccountDesc: '使用するアカウントを選択',
stepVariantDesc: 'カスタムバリアントを作成',
stepVariantDesc: '任意: カスタムランタイムバリアントを作成',
stepSuccessDesc: 'セットアップ完了!',
authStep: {
authenticateWith: '{{provider}} で認証してアカウントを追加',
+85 -16
View File
@@ -10,7 +10,7 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Skeleton } from '@/components/ui/skeleton';
import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2 } from 'lucide-react';
import { Check, X, RefreshCw, Plus, Zap, GitBranch, Trash2 } from 'lucide-react';
import { QuickSetupWizard } from '@/components/quick-setup-wizard';
import { AddAccountDialog } from '@/components/account/add-account-dialog';
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
@@ -183,7 +183,13 @@ function VariantSidebarItem({
}
// Empty state for right panel
function EmptyProviderState({ onSetup }: { onSetup: () => void }) {
function EmptyProviderState({
onAddAccount,
onCreateVariant,
}: {
onAddAccount: () => void;
onCreateVariant: () => void;
}) {
const { t } = useTranslation();
return (
<div className="flex-1 flex items-center justify-center bg-muted/20">
@@ -200,10 +206,16 @@ function EmptyProviderState({ onSetup }: { onSetup: () => void }) {
</a>
.
</p>
<Button onClick={onSetup} className="gap-2">
<Sparkles className="w-4 h-4" />
{t('cliproxyPage.quickSetup')}
</Button>
<div className="flex flex-col justify-center gap-2 sm:flex-row">
<Button onClick={onAddAccount} className="gap-2">
<Plus className="w-4 h-4" />
{t('cliproxyPage.addAccount')}
</Button>
<Button onClick={onCreateVariant} variant="outline" className="gap-2">
<GitBranch className="w-4 h-4" />
{t('cliproxyPage.advancedVariant')}
</Button>
</div>
</div>
</div>
);
@@ -305,6 +317,38 @@ export function CliproxyPage() {
const parentAuthForVariant = selectedVariantData
? providers.find((p) => p.provider === selectedVariantData.provider)
: undefined;
const selectedAccountTarget =
selectedVariantData && parentAuthForVariant
? {
provider: selectedVariantData.provider,
displayName: parentAuthForVariant.displayName,
accountCount: parentAuthForVariant.accounts?.length || 0,
}
: selectedStatus
? {
provider: selectedStatus.provider,
displayName: selectedStatus.displayName,
accountCount: selectedStatus.accounts?.length || 0,
}
: null;
const fallbackAccountTarget = selectedVariantData
? {
provider: selectedVariantData.provider,
displayName: getProviderDisplayName(selectedVariantData.provider),
accountCount: 0,
}
: selectedProvider && isValidProvider(selectedProvider)
? {
provider: selectedProvider,
displayName: getProviderDisplayName(selectedProvider),
accountCount: 0,
}
: {
provider: 'gemini',
displayName: getProviderDisplayName('gemini'),
accountCount: 0,
};
const accountSetupTarget = selectedAccountTarget ?? fallbackAccountTarget;
const warningProvider = (selectedVariantData?.provider || selectedStatus?.provider || '')
.toLowerCase()
.trim();
@@ -352,6 +396,14 @@ export function CliproxyPage() {
setSelectedProvider(null);
};
const handleAddAccountForSelectedProvider = () => {
setAddAccountProvider({
provider: accountSetupTarget.provider,
displayName: accountSetupTarget.displayName,
isFirstAccount: accountSetupTarget.accountCount === 0,
});
};
return (
<div className="flex h-full min-h-0 overflow-hidden">
{/* Left Sidebar */}
@@ -377,15 +429,29 @@ export function CliproxyPage() {
{t('cliproxyPage.accountManagement')}
</p>
<Button
variant="default"
size="sm"
className="w-full gap-2"
onClick={() => setWizardOpen(true)}
>
<Sparkles className="w-4 h-4" />
{t('cliproxyPage.quickSetup')}
</Button>
<div className="space-y-2">
<Button
variant="default"
size="sm"
className="w-full gap-2"
onClick={handleAddAccountForSelectedProvider}
>
<Plus className="w-4 h-4" />
{t('cliproxyPage.addAccount')}
</Button>
<Button
variant="outline"
size="sm"
className="w-full gap-2"
onClick={() => setWizardOpen(true)}
>
<GitBranch className="w-4 h-4" />
{t('cliproxyPage.advancedVariant')}
</Button>
<p className="text-[11px] leading-relaxed text-muted-foreground">
{t('cliproxyPage.setupActionsHint')}
</p>
</div>
</div>
{/* Providers List */}
@@ -577,7 +643,10 @@ export function CliproxyPage() {
/>
</>
) : (
<EmptyProviderState onSetup={() => setWizardOpen(true)} />
<EmptyProviderState
onAddAccount={handleAddAccountForSelectedProvider}
onCreateVariant={() => setWizardOpen(true)}
/>
)}
</div>
@@ -15,6 +15,7 @@ const hookState = vi.hoisted(() => ({
>;
}
| undefined,
startAuthData: {} as Record<string, unknown>,
}));
const applyDefaultPresetMock = vi.hoisted(() => vi.fn());
@@ -43,7 +44,7 @@ vi.mock('@/hooks/use-cliproxy', () => ({
useStartAuth: () => ({
isPending: false,
mutate: (_args: unknown, options?: { onSuccess?: (data: Record<string, unknown>) => void }) => {
options?.onSuccess?.({});
options?.onSuccess?.(hookState.startAuthData);
},
}),
useCancelAuth: () => ({
@@ -72,7 +73,22 @@ vi.mock('@/components/setup/wizard/steps/account-step', () => ({
}));
vi.mock('@/components/setup/wizard/steps/variant-step', () => ({
VariantStep: () => <div>variant-step</div>,
VariantStep: ({
variantName,
onVariantNameChange,
}: {
variantName: string;
onVariantNameChange: (value: string) => void;
}) => (
<label>
Runtime Variant Name
<input
aria-label="Runtime Variant Name"
value={variantName}
onChange={(event) => onVariantNameChange(event.target.value)}
/>
</label>
),
}));
vi.mock('@/components/setup/wizard/steps/success-step', () => ({
@@ -84,6 +100,7 @@ import { QuickSetupWizard } from '@/components/setup/wizard';
describe('QuickSetupWizard preset catalog reuse', () => {
beforeEach(() => {
hookState.catalogData = undefined;
hookState.startAuthData = {};
applyDefaultPresetMock.mockReset();
applyDefaultPresetMock.mockResolvedValue({ success: true, presetName: 'Gemini Pro' });
});
@@ -126,4 +143,10 @@ describe('QuickSetupWizard preset catalog reuse', () => {
)
);
});
it('labels the wizard as advanced variant setup', () => {
render(<QuickSetupWizard open onClose={vi.fn()} />);
expect(screen.getByRole('dialog')).toHaveTextContent('Advanced Variant Setup');
});
});
+66 -4
View File
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, userEvent } from '@tests/setup/test-utils';
const hookState = vi.hoisted(() => ({
variantsData: { variants: [] as Array<{ name: string; provider: string; target?: string }> },
authData: {
authStatus: [
{
@@ -36,7 +37,7 @@ const hookState = vi.hoisted(() => ({
vi.mock('@/hooks/use-cliproxy', () => ({
useCliproxy: () => ({
data: { variants: [] },
data: hookState.variantsData,
isFetching: false,
}),
useCliproxyAuth: () => ({
@@ -60,7 +61,8 @@ vi.mock('@/hooks/use-cliproxy', () => ({
}));
vi.mock('@/components/quick-setup-wizard', () => ({
QuickSetupWizard: () => null,
QuickSetupWizard: ({ open }: { open: boolean }) =>
open ? <div data-testid="advanced-variant-wizard" /> : null,
}));
vi.mock('@/components/monitoring/proxy-status-widget', () => ({
@@ -82,9 +84,21 @@ vi.mock('@/components/cliproxy/provider-editor', () => ({
}));
vi.mock('@/components/account/add-account-dialog', () => ({
AddAccountDialog: ({ open, catalog }: { open: boolean; catalog?: { defaultModel?: string } }) =>
AddAccountDialog: ({
open,
provider,
catalog,
}: {
open: boolean;
provider?: string;
catalog?: { defaultModel?: string };
}) =>
open ? (
<div data-testid="add-account-dialog" data-catalog={catalog?.defaultModel ?? 'missing'} />
<div
data-testid="add-account-dialog"
data-provider={provider}
data-catalog={catalog?.defaultModel ?? 'missing'}
/>
) : null,
}));
@@ -92,6 +106,7 @@ import { CliproxyPage } from '@/pages/cliproxy';
describe('CliproxyPage add-account catalog gating', () => {
beforeEach(() => {
hookState.variantsData = { variants: [] };
hookState.authData = {
authStatus: [
{
@@ -154,4 +169,51 @@ describe('CliproxyPage add-account catalog gating', () => {
'gemini-3.9-pro-preview'
);
});
it('uses the sidebar setup CTA for account connection instead of variant creation', async () => {
render(<CliproxyPage />);
await userEvent.click(screen.getByRole('button', { name: /add account/i }));
expect(screen.getByTestId('add-account-dialog')).toHaveAttribute('data-provider', 'gemini');
expect(screen.queryByTestId('advanced-variant-wizard')).not.toBeInTheDocument();
});
it('adds accounts against the base provider when a runtime variant is selected', async () => {
hookState.variantsData = {
variants: [{ name: 'gemini-parallel', provider: 'gemini', target: 'claude' }],
};
render(<CliproxyPage />);
await userEvent.click(screen.getByRole('button', { name: /gemini-parallel/i }));
await userEvent.click(screen.getByRole('button', { name: /add account/i }));
expect(screen.getByTestId('add-account-dialog')).toHaveAttribute('data-provider', 'gemini');
expect(screen.queryByTestId('advanced-variant-wizard')).not.toBeInTheDocument();
});
it('surfaces account setup from the empty state before provider data is available', async () => {
hookState.authData = {
authStatus: [],
source: 'local',
};
render(<CliproxyPage />);
const addAccountButtons = screen.getAllByRole('button', { name: /add account/i });
await userEvent.click(addAccountButtons[addAccountButtons.length - 1]);
expect(screen.getByTestId('add-account-dialog')).toHaveAttribute('data-provider', 'gemini');
expect(screen.queryByTestId('advanced-variant-wizard')).not.toBeInTheDocument();
});
it('keeps advanced variant creation available as a secondary action', async () => {
render(<CliproxyPage />);
await userEvent.click(screen.getByRole('button', { name: /advanced variant/i }));
expect(screen.getByTestId('advanced-variant-wizard')).toBeInTheDocument();
expect(screen.queryByTestId('add-account-dialog')).not.toBeInTheDocument();
});
});