@@ -137,9 +146,13 @@ export function AccountsTable({ data, defaultAccount, groupSummaries }: Accounts
}
return (
-
-
{t('accountsTable.isolated')}
-
{t('accountsTable.noHandoff')}
+
+
+ Isolated
+
);
},
diff --git a/ui/src/components/account/continuity-overview.tsx b/ui/src/components/account/continuity-overview.tsx
new file mode 100644
index 00000000..89a1df93
--- /dev/null
+++ b/ui/src/components/account/continuity-overview.tsx
@@ -0,0 +1,238 @@
+import {
+ AlertTriangle,
+ CheckCircle2,
+ Info,
+ Link2,
+ ShieldAlert,
+ Unlink,
+ Waves,
+ ArrowRight,
+} from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+
+interface ContinuityOverviewProps {
+ totalAccounts: number;
+ isolatedCount: number;
+ sharedStandardCount: number;
+ deeperSharedCount: number;
+ sharedAloneCount: number;
+ sharedPeerAccountCount: number; // The accounts in a viable group
+ deeperReadyAccountCount: number;
+ sharedGroups: string[];
+ sharedPeerGroups: string[];
+ deeperReadyGroups: string[];
+ legacyTargetCount: number;
+ cliproxyCount: number;
+}
+
+type ReadinessState =
+ | 'single'
+ | 'isolated'
+ | 'shared-alone'
+ | 'shared-standard'
+ | 'partial'
+ | 'ready';
+
+const InfoTooltip = ({ titleKey, descKey }: { titleKey: string; descKey: string }) => {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+ {t(titleKey)}
+ {t(descKey)}
+
+
+ );
+};
+
+export function ContinuityOverview({
+ totalAccounts,
+ isolatedCount,
+ sharedStandardCount,
+ deeperSharedCount,
+ sharedAloneCount,
+ sharedPeerAccountCount,
+ deeperReadyAccountCount,
+ sharedPeerGroups,
+ deeperReadyGroups,
+ legacyTargetCount,
+ cliproxyCount,
+}: ContinuityOverviewProps) {
+ const { t } = useTranslation();
+
+ const readiness: ReadinessState =
+ totalAccounts < 2
+ ? 'single'
+ : sharedPeerGroups.length === 0
+ ? isolatedCount === totalAccounts
+ ? 'isolated'
+ : 'shared-alone'
+ : deeperReadyGroups.length === 0
+ ? 'shared-standard'
+ : isolatedCount > 0 ||
+ sharedAloneCount > 0 ||
+ deeperReadyAccountCount < sharedPeerAccountCount ||
+ deeperReadyGroups.length < sharedPeerGroups.length
+ ? 'partial'
+ : 'ready';
+
+ const highlightGroup = deeperReadyGroups[0] || sharedPeerGroups[0] || 'default';
+ const hasMixedState =
+ deeperReadyGroups.length > 0 &&
+ (isolatedCount > 0 ||
+ sharedStandardCount > 0 ||
+ sharedPeerGroups.length > deeperReadyGroups.length);
+
+ const iconMap = {
+ ready:
,
+ 'shared-standard':
,
+ single:
,
+ isolated:
,
+ 'shared-alone':
,
+ partial:
,
+ };
+
+ const currentIcon = iconMap[readiness];
+
+ return (
+
+ {/* Primary Status Bento Box */}
+
+
+
+
+
+ {currentIcon}
+
+
+
+
+ {t(`continuityReadiness.messages.${readiness}.title`, {
+ group: highlightGroup,
+ })}
+
+
+ {t(`continuityReadiness.state.${readiness}`)}
+
+
+
+ {t(`continuityReadiness.messages.${readiness}.description`, {
+ group: highlightGroup,
+ count: sharedAloneCount,
+ })}
+
+
+
+
+
+
+ {cliproxyCount > 0 && (
+
+ {t('historySyncLearningMap.cliproxyManaged', { count: cliproxyCount })}
+
+ )}
+ {legacyTargetCount > 0 && (
+
+ {t('historySyncLearningMap.legacyConfirmation', { count: legacyTargetCount })}
+
+ )}
+ {sharedPeerGroups.length > 0 && deeperReadyGroups.length === 0 && (
+
+ Recommend: {highlightGroup}
+
+ )}
+ {hasMixedState && (
+
+ Partial sync ({highlightGroup})
+
+ )}
+
+
+
+
+ {/* Horizontal Progression Chain */}
+
+
+
+
+
+ {t('historySyncLearningMap.isolated')}
+
+
+
+
+ {isolatedCount}
+
+
+
+
+
+
+
+
+
+ {t('historySyncLearningMap.shared')}
+
+
+
+
+ {sharedStandardCount}
+
+
+
+
+
+
+
+
+
+ {t('historySyncLearningMap.deeper')}
+
+
+
+
+ {deeperSharedCount}
+
+
+
+
+ );
+}
diff --git a/ui/src/components/account/continuity-readiness-card.tsx b/ui/src/components/account/continuity-readiness-card.tsx
deleted file mode 100644
index 2cd029e5..00000000
--- a/ui/src/components/account/continuity-readiness-card.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { AlertTriangle, CheckCircle2, Link2, ShieldAlert } from 'lucide-react';
-import { useTranslation } from 'react-i18next';
-import { Badge } from '@/components/ui/badge';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-
-interface ContinuityReadinessCardProps {
- totalAccounts: number;
- isolatedCount: number;
- sharedAloneCount: number;
- sharedPeerAccountCount: number;
- deeperReadyAccountCount: number;
- sharedPeerGroups: string[];
- deeperReadyGroups: string[];
-}
-
-type ReadinessState =
- | 'single'
- | 'isolated'
- | 'shared-alone'
- | 'shared-standard'
- | 'partial'
- | 'ready';
-
-export function ContinuityReadinessCard({
- totalAccounts,
- isolatedCount,
- sharedAloneCount,
- sharedPeerAccountCount,
- deeperReadyAccountCount,
- sharedPeerGroups,
- deeperReadyGroups,
-}: ContinuityReadinessCardProps) {
- const { t } = useTranslation();
-
- const readiness: ReadinessState =
- totalAccounts < 2
- ? 'single'
- : sharedPeerGroups.length === 0
- ? isolatedCount === totalAccounts
- ? 'isolated'
- : 'shared-alone'
- : deeperReadyGroups.length === 0
- ? 'shared-standard'
- : isolatedCount > 0 ||
- sharedAloneCount > 0 ||
- deeperReadyAccountCount < sharedPeerAccountCount ||
- deeperReadyGroups.length < sharedPeerGroups.length
- ? 'partial'
- : 'ready';
-
- const highlightGroup = deeperReadyGroups[0] || sharedPeerGroups[0] || 'default';
- const icon =
- readiness === 'ready' ? (
-
- ) : readiness === 'shared-standard' ? (
-
- ) : readiness === 'single' ? (
-
- ) : (
-
- );
- const stepItems =
- readiness === 'single'
- ? [
- t('continuityReadiness.singleSteps.addAccount'),
- t('continuityReadiness.singleSteps.sameGroupLater'),
- t('continuityReadiness.singleSteps.enableDeeperLater'),
- t('continuityReadiness.singleSteps.resumeOriginal'),
- ]
- : [
- t('continuityReadiness.steps.syncBoth'),
- t('continuityReadiness.steps.sameGroup', { group: highlightGroup }),
- t('continuityReadiness.steps.enableDeeper'),
- t('continuityReadiness.steps.resumeOriginal'),
- ];
-
- return (
-
-
-
-
- {t('continuityReadiness.title')}
- {t('continuityReadiness.description')}
-
-
- {t(`continuityReadiness.state.${readiness}`)}
-
-
-
-
-
-
-
- {t('continuityReadiness.metrics.isolated')}
-
-
{isolatedCount}
-
-
-
- {t('continuityReadiness.metrics.sharedPeers')}
-
-
{sharedPeerAccountCount}
-
-
-
- {t('continuityReadiness.metrics.deeperReady')}
-
-
{deeperReadyAccountCount}
-
-
-
-
-
-
{icon}
-
-
- {t(`continuityReadiness.messages.${readiness}.title`, { group: highlightGroup })}
-
-
- {t(`continuityReadiness.messages.${readiness}.description`, {
- group: highlightGroup,
- count: sharedAloneCount,
- })}
-
-
-
-
-
-
-
{t('continuityReadiness.stepsTitle')}
-
- {stepItems.map((item) => (
- - {item}
- ))}
-
-
-
-
- );
-}
diff --git a/ui/src/components/account/edit-account-context-dialog.tsx b/ui/src/components/account/edit-account-context-dialog.tsx
index b10b9538..b4dc2aaf 100644
--- a/ui/src/components/account/edit-account-context-dialog.tsx
+++ b/ui/src/components/account/edit-account-context-dialog.tsx
@@ -10,13 +10,8 @@ import {
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
+
+import { Unlink, Link2, Waves, ShieldAlert, Info } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { AuthAccountRow, SharedGroupSummary } from '@/lib/account-continuity';
import { useUpdateAccountContext } from '@/hooks/use-accounts';
@@ -117,20 +112,47 @@ export function EditAccountContextDialog({
-
-
-
- {mode === 'shared'
- ? t('editAccountContext.sharedModeHint')
- : t('editAccountContext.isolatedModeHint')}
+
+
+
+
+
+
+
+
+ {mode === 'isolated'
+ ? t('editAccountContext.isolatedModeHint')
+ : t('editAccountContext.sharedModeHint')}
@@ -155,23 +177,49 @@ export function EditAccountContextDialog({
{mode === 'shared' && (
-
-
)}
@@ -180,33 +228,51 @@ export function EditAccountContextDialog({
{t('editAccountContext.credentialsIsolated')}
-
-
- {t('editAccountContext.implicationTitle')}
-
-
+
+
{mode === 'isolated' ? (
-
{t('editAccountContext.isolatedImplication')}
+
) : (
- <>
-
{t('editAccountContext.sameGroupRule', { group: normalizedGroup })}
-
- {sameGroupPeerCount > 0
- ? t('editAccountContext.sameGroupPeerCount', { count: sameGroupPeerCount })
- : t('editAccountContext.noSameGroupPeer')}
+
+ )}
+
+ {mode === 'isolated' ? (
+
+ {t('editAccountContext.isolatedImplication')}
-
- {continuityMode === 'deeper'
- ? sameGroupDeeperPeerCount > 0
- ? t('editAccountContext.deeperReady', {
+ ) : (
+ <>
+
+
+ {t('editAccountContext.sameGroupRule', { group: normalizedGroup })}
+ {' '}
+ {sameGroupPeerCount > 0
+ ? t('editAccountContext.sameGroupPeerCount', { count: sameGroupPeerCount })
+ : t('editAccountContext.noSameGroupPeer')}
+
+ {continuityMode === 'deeper' && (
+
+ {sameGroupDeeperPeerCount > 0 ? (
+ t('editAccountContext.deeperReady', {
count: sameGroupDeeperPeerCount,
})
- : t('editAccountContext.deeperNeedsPeers')
- : t('editAccountContext.standardWarning')}
-
- >
- )}
-
{t('editAccountContext.resumeOriginalWarning')}
+ ) : (
+
+ {t('editAccountContext.deeperNeedsPeers')}
+
+ )}
+
+ )}
+ >
+ )}
+
+ {t('editAccountContext.resumeOriginalWarning')}
+
+
diff --git a/ui/src/components/account/history-sync-learning-map.tsx b/ui/src/components/account/history-sync-learning-map.tsx
deleted file mode 100644
index 50071c02..00000000
--- a/ui/src/components/account/history-sync-learning-map.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { useState } from 'react';
-import {
- ArrowRight,
- ArrowRightLeft,
- ChevronDown,
- Layers3,
- Link2,
- Unlink,
- Waves,
- type LucideIcon,
-} from 'lucide-react';
-import { Badge } from '@/components/ui/badge';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
-import { useTranslation } from 'react-i18next';
-import { cn } from '@/lib/utils';
-
-interface HistorySyncLearningMapProps {
- isolatedCount: number;
- sharedStandardCount: number;
- deeperSharedCount: number;
- sharedGroups: string[];
- sharedPeerGroups: string[];
- deeperReadyGroups: string[];
- legacyTargetCount: number;
- cliproxyCount: number;
-}
-
-type StageTone = 'isolated' | 'shared' | 'deeper';
-
-function StageTile({
- title,
- count,
- icon: Icon,
- tone,
-}: {
- title: string;
- count: number;
- icon: LucideIcon;
- tone: StageTone;
-}) {
- const toneClasses: Record
= {
- isolated: {
- border: 'border-blue-300/60 bg-blue-50/40 dark:border-blue-900/40 dark:bg-blue-900/10',
- icon: 'text-blue-700 dark:text-blue-400',
- count: 'text-blue-700 dark:text-blue-400',
- },
- shared: {
- border:
- 'border-emerald-300/60 bg-emerald-50/40 dark:border-emerald-900/40 dark:bg-emerald-900/10',
- icon: 'text-emerald-700 dark:text-emerald-400',
- count: 'text-emerald-700 dark:text-emerald-400',
- },
- deeper: {
- border:
- 'border-indigo-300/60 bg-indigo-50/40 dark:border-indigo-900/40 dark:bg-indigo-900/10',
- icon: 'text-indigo-700 dark:text-indigo-400',
- count: 'text-indigo-700 dark:text-indigo-400',
- },
- };
-
- return (
-
- );
-}
-
-export function HistorySyncLearningMap({
- isolatedCount,
- sharedStandardCount,
- deeperSharedCount,
- sharedGroups,
- sharedPeerGroups,
- deeperReadyGroups,
- legacyTargetCount,
- cliproxyCount,
-}: HistorySyncLearningMapProps) {
- const { t } = useTranslation();
- const [open, setOpen] = useState(false);
- const groupsToShow = sharedGroups.length > 0 ? sharedGroups : ['default'];
- const highlightGroup = deeperReadyGroups[0] || sharedPeerGroups[0] || groupsToShow[0];
- const hasMixedState =
- deeperReadyGroups.length > 0 &&
- (isolatedCount > 0 ||
- sharedStandardCount > 0 ||
- sharedPeerGroups.length > deeperReadyGroups.length);
-
- return (
-
-
-
-
- {t('historySyncLearningMap.title')}
-
- {t('historySyncLearningMap.description')}
-
-
-
{t('historySyncLearningMap.learningMap')}
-
-
-
-
- {cliproxyCount > 0 && (
-
- {t('historySyncLearningMap.cliproxyManaged', { count: cliproxyCount })}
-
- )}
-
-
-
-
- {sharedPeerGroups.length === 0
- ? t('historySyncLearningMap.sameGroupRule')
- : deeperReadyGroups.length === 0
- ? t('historySyncLearningMap.deeperRecommendation', { group: highlightGroup })
- : hasMixedState
- ? t('historySyncLearningMap.partialGroup', { group: highlightGroup })
- : t('historySyncLearningMap.readyGroup', { group: highlightGroup })}
-
-
-
-
-
-
-
-
-
-
-
-
{t('historySyncLearningMap.modeSwitch')}
-
-
- {t('historySyncLearningMap.modeSwitchDesc')}
-
-
-
-
-
-
-
{t('historySyncLearningMap.historySyncGroup')}
-
-
- {t('historySyncLearningMap.historySyncGroupDesc')}
-
-
- {groupsToShow.map((group) => (
-
- {group}
-
- ))}
-
-
-
-
- {legacyTargetCount > 0 && (
-
- {t('historySyncLearningMap.legacyConfirmation', { count: legacyTargetCount })}
-
- )}
-
-
-
-
- );
-}
diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts
index e9bdc9c6..f4cd1e01 100644
--- a/ui/src/lib/i18n.ts
+++ b/ui/src/lib/i18n.ts
@@ -401,12 +401,10 @@ const resources = {
'Configure how "{{name}}" shares history and continuity with other ccs auth accounts.',
syncMode: 'Sync Mode',
selectContextMode: 'Select context mode',
- isolatedOption: 'isolated (no sync)',
- sharedOption: 'shared (sync enabled)',
- sharedModeHint:
- 'Shared mode reuses workspace context for accounts in the same history sync group.',
- isolatedModeHint:
- 'Isolated mode keeps this account fully separate from other ccs auth accounts.',
+ isolatedOption: 'Isolated',
+ sharedOption: 'Shared',
+ sharedModeHint: 'Reuses workspace context across accounts in the same history group.',
+ isolatedModeHint: 'Keeps this account fully separate from other ccs auth accounts.',
historySyncGroup: 'History Sync Group',
groupPlaceholder: 'default',
groupHint:
@@ -414,10 +412,9 @@ const resources = {
invalidGroup: 'Enter a valid group name that starts with a letter.',
continuityDepth: 'Continuity Depth',
selectContinuityDepth: 'Select continuity depth',
- standardOption: 'standard (projects only)',
- deeperOption: 'deeper continuity (advanced)',
- deeperHint:
- 'Advanced mode also syncs session-env, file-history, shell-snapshots, and todos.',
+ standardOption: 'Standard',
+ deeperOption: 'Deeper',
+ deeperHint: 'Syncs comprehensive session-env, file-history, shell-snapshots, and todos.',
standardHint: 'Standard mode syncs project workspace context only.',
credentialsIsolated: 'Credentials and .anthropic remain isolated per account in all modes.',
implicationTitle: 'What this means after save',
diff --git a/ui/src/pages/accounts.tsx b/ui/src/pages/accounts.tsx
index c649f611..ddcad6db 100644
--- a/ui/src/pages/accounts.tsx
+++ b/ui/src/pages/accounts.tsx
@@ -5,19 +5,16 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import { AlertTriangle, ArrowRight, ChevronDown, Plus, Users, Zap } from 'lucide-react';
+import { AlertTriangle, ArrowRight, Plus, Users, Zap } from 'lucide-react';
import { AccountsTable } from '@/components/account/accounts-table';
-import { ContinuityReadinessCard } from '@/components/account/continuity-readiness-card';
+import { ContinuityOverview } from '@/components/account/continuity-overview';
import { CreateAuthProfileDialog } from '@/components/account/create-auth-profile-dialog';
-import { HistorySyncLearningMap } from '@/components/account/history-sync-learning-map';
import { CopyButton } from '@/components/ui/copy-button';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useAccounts, useConfirmLegacyAccountPolicies } from '@/hooks/use-accounts';
-import { cn } from '@/lib/utils';
import { useTranslation } from 'react-i18next';
export function AccountsPage() {
@@ -26,7 +23,6 @@ export function AccountsPage() {
const { data, isLoading } = useAccounts();
const confirmLegacyMutation = useConfirmLegacyAccountPolicies();
const [createDialogOpen, setCreateDialogOpen] = useState(false);
- const [guideOpen, setGuideOpen] = useState(false);
const authAccounts = data?.accounts || [];
const cliproxyCount = data?.cliproxyCount || 0;
@@ -146,50 +142,6 @@ export function AccountsPage() {
)}
-
-
-
-
-
-
-
-
-
-
-
- {t('accountsPage.sharedStandard')}
-
-
{t('accountsPage.sharedStandardDesc')}
-
-
-
- {t('accountsPage.sharedDeeper')}
-
-
{t('accountsPage.sharedDeeperDesc')}
-
-
-
- {t('accountsPage.isolated')}
-
-
{t('accountsPage.isolatedDesc')}
-
-
-
-
-
-
{t('accountsPage.quickCommands')}
@@ -235,20 +187,14 @@ export function AccountsPage() {
-
-
-
-
-
-
{t('accountsPage.accountMatrix')}
diff --git a/ui/tests/unit/ui/i18n/language-switcher.test.tsx b/ui/tests/unit/ui/i18n/language-switcher.test.tsx
index 84be5f7e..d8618b21 100644
--- a/ui/tests/unit/ui/i18n/language-switcher.test.tsx
+++ b/ui/tests/unit/ui/i18n/language-switcher.test.tsx
@@ -1,9 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, userEvent, waitFor } from '@tests/setup/test-utils';
import i18n from '@/lib/i18n';
-import { ContinuityReadinessCard } from '@/components/account/continuity-readiness-card';
+import { ContinuityOverview } from '@/components/account/continuity-overview';
import { LanguageSwitcher } from '@/components/layout/language-switcher';
-import { HistorySyncLearningMap } from '@/components/account/history-sync-learning-map';
import { TabNavigation } from '@/pages/settings/components/tab-navigation';
import {
getInitialLocale,
@@ -138,97 +137,50 @@ describe('Dashboard i18n', () => {
expect(screen.getByText('思考')).toBeInTheDocument();
});
- it('renders Japanese history sync guidance without fallback English copy', async () => {
- await i18n.changeLanguage('ja');
-
- render(
-
- );
-
- expect(screen.getByText('履歴同期の仕組み')).toBeInTheDocument();
- expect(
- screen.getByText(
- 'グループ「default」は良い状態ですが、他のアカウントやグループでは同グループ設定や拡張継続性がまだ不足しています。'
- )
- ).toBeInTheDocument();
- expect(
- screen.getByText('1 件の CLIProxy Claude Pool アカウントは、CLIProxy ページで管理します。')
- ).toBeInTheDocument();
-
- await userEvent.click(
- screen.getByRole('button', { name: '詳細を表示: グループ、切り替え、レガシーポリシー' })
- );
-
- expect(
- screen.getByText('2 件のレガシーアカウントで明示的な確認がまだ必要です。')
- ).toBeInTheDocument();
- });
-
- it('renders Japanese continuity readiness card without fallback English copy', async () => {
- await i18n.changeLanguage('ja');
-
- render(
-
- );
-
- expect(screen.getByText('アカウント間再開チェック')).toBeInTheDocument();
- expect(screen.getByText('現在、アカウント間再開はオフです。')).toBeInTheDocument();
- });
-
- it('uses single-account-specific next steps in the readiness card', async () => {
+ it('uses single-account-specific next steps in the continuity overview', async () => {
await i18n.changeLanguage('en');
render(
-
);
expect(
screen.getByText(
- 'Create a second ccs auth account before planning any cross-account handoff.'
+ 'Cross-account handoff does not apply yet. Add another auth account before configuring shared continuity.'
)
).toBeInTheDocument();
- expect(
- screen.queryByText('Open Sync on both accounts, not just the one you are switching into.')
- ).not.toBeInTheDocument();
});
it('pluralizes the shared-alone readiness copy', async () => {
await i18n.changeLanguage('en');
render(
-
);
@@ -237,9 +189,9 @@ describe('Dashboard i18n', () => {
).toBeInTheDocument();
});
- it.each(SUPPORTED_LOCALES.filter((locale) => locale !== 'en'))(
+ it.each(SUPPORTED_LOCALES.filter((locale: string) => locale !== 'en'))(
'keeps %s translation keys in parity with en and preserves placeholders',
- (locale) => {
+ (locale: string) => {
const resources = i18n.options.resources as
| Record }>
| undefined;