From 6cfc8d4a45fcaf80f413dd1b397e5f7f46f889d0 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 26 Feb 2026 20:59:16 +0700 Subject: [PATCH] feat(accounts-ui): add history sync learning visualization --- .../account/history-sync-learning-map.tsx | 163 ++++++++++++++++++ ui/src/pages/accounts.tsx | 37 ++-- 2 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 ui/src/components/account/history-sync-learning-map.tsx diff --git a/ui/src/components/account/history-sync-learning-map.tsx b/ui/src/components/account/history-sync-learning-map.tsx new file mode 100644 index 00000000..b63b53a2 --- /dev/null +++ b/ui/src/components/account/history-sync-learning-map.tsx @@ -0,0 +1,163 @@ +import { + ArrowRight, + ArrowRightLeft, + Layers3, + Link2, + Unlink, + Waves, + type LucideIcon, +} from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; + +interface HistorySyncLearningMapProps { + isolatedCount: number; + sharedStandardCount: number; + deeperSharedCount: number; + sharedGroups: string[]; + legacyTargetCount: number; +} + +type StageTone = 'isolated' | 'shared' | 'deeper'; + +function StageCard({ + title, + count, + icon: Icon, + tone, + description, +}: { + title: string; + count: number; + icon: LucideIcon; + tone: StageTone; + description: string; +}) { + const toneClasses: Record = { + isolated: { + card: '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: { + card: '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: { + card: '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 ( +
+
+

{title}

+ +
+

+ {count} +

+

{description}

+
+ ); +} + +export function HistorySyncLearningMap({ + isolatedCount, + sharedStandardCount, + deeperSharedCount, + sharedGroups, + legacyTargetCount, +}: HistorySyncLearningMapProps) { + const groupsToShow = sharedGroups.length > 0 ? sharedGroups : ['default']; + + return ( + + +
+ How History Sync Works + Learning Map +
+ + Accounts can move between modes at any time. Link/unlink is instant; Edit gives full + control of group and continuity depth. + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +

Mode Switch Actions

+
+
+ Link: Isolated -> Shared + Unlink: Shared -> Isolated + Edit: Group + Deeper +
+
+ +
+
+ +

History Sync Group

+
+

+ Accounts in the same group share the same project context bucket. Group names are + user-defined lanes, with default as fallback. +

+
+ {groupsToShow.map((group) => ( + + {group} + + ))} +
+
+
+ + {legacyTargetCount > 0 && ( +
+ {legacyTargetCount} legacy account + {legacyTargetCount > 1 ? 's still need' : ' still needs'} explicit confirmation. Use{' '} + Confirm Legacy Policies in Action Center. +
+ )} +
+
+ ); +} diff --git a/ui/src/pages/accounts.tsx b/ui/src/pages/accounts.tsx index ab1d2e02..0f1b603a 100644 --- a/ui/src/pages/accounts.tsx +++ b/ui/src/pages/accounts.tsx @@ -19,6 +19,7 @@ import { } from 'lucide-react'; import { AccountsTable } from '@/components/account/accounts-table'; 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'; @@ -115,6 +116,13 @@ export function AccountsPage() { const sharedStandardCount = data?.sharedStandardCount || 0; const deeperSharedCount = data?.deeperSharedCount || 0; const isolatedCount = data?.isolatedCount || 0; + const sharedGroups = Array.from( + new Set( + authAccounts + .filter((account) => account.context_mode === 'shared') + .map((account) => account.context_group || 'default') + ) + ).sort((a, b) => a.localeCompare(b)); const legacyTargets = authAccounts.filter( (account) => account.context_inferred || account.continuity_inferred @@ -149,25 +157,10 @@ export function AccountsPage() { Create Account - - +

+ Pool auth actions live in Action Center to avoid duplicate controls. +

@@ -302,6 +295,14 @@ export function AccountsPage() { )} + + Account Matrix