From b2083b7fed220f4d79027c199dc44c0c0c185c7c Mon Sep 17 00:00:00 2001 From: Cedrick Joseph Mariano <166236276+icodecedd@users.noreply.github.com> Date: Tue, 23 Jun 2026 00:25:47 +0800 Subject: [PATCH 1/5] feat(theme): refactor theme loader, add single-variant theme support and new built-in themes --- src/App.tsx | 27 +- .../dialogs/WorkspaceSwitcherDialog.tsx | 423 ++++-- src/components/layout/AppHeader.tsx | 2 +- .../screens/FirstRunOnboardingScreen.tsx | 14 +- src/components/screens/ModeSelectorScreen.tsx | 561 ++++---- src/components/screens/onboarding/BootLog.tsx | 31 +- .../screens/onboarding/StepActivation.tsx | 13 +- .../screens/onboarding/StepChoice.tsx | 68 +- .../screens/onboarding/StepFoundation.tsx | 286 ++-- .../screens/onboarding/StepPickProfile.tsx | 103 +- src/components/ui/layout-grid-preview.tsx | 22 +- src/components/ui/layout-preview-icon.tsx | 39 +- src/context/WorkspaceContext.tsx | 325 +++-- .../cortex-library/CortexLibraryDialog.tsx | 1208 ++++++++++------- .../cortex-library/components/PresetsTab.tsx | 330 +++-- .../cortex-library/components/SnippetsTab.tsx | 471 ++++--- .../components/WorkspacesTab.tsx | 362 +++-- .../settings/components/tabs/ThemesTab.tsx | 45 +- src/features/setup/SetupView.tsx | 366 ++--- .../setup/components/steps/StepConfigure.tsx | 79 +- .../setup/components/steps/StepWorkspace.tsx | 136 +- .../components/ui-parts/PaneConfigCard.tsx | 187 ++- .../components/ui-parts/PresetManager.tsx | 30 +- src/features/space/SpaceView.tsx | 486 ++++--- .../terminal/components/XtermTerminal.tsx | 70 +- src/hooks/useAgents.ts | 309 +++-- src/hooks/useAppShortcuts.ts | 76 +- src/hooks/usePresets.ts | 55 +- src/hooks/useSetupPanes.ts | 220 +-- src/hooks/useSnippets.ts | 124 +- src/hooks/useSpaceTemplates.ts | 190 +-- src/hooks/useTheme.ts | 456 ++----- src/{types => lib}/index.ts | 0 src/{types => lib}/onboarding.ts | 17 +- src/lib/setup-constants.ts | 26 +- src/lib/setup-utils.ts | 263 ++-- src/lib/theme-types.ts | 55 + src/themes/caffeine.ts | 118 ++ src/themes/catppuccin.ts | 116 ++ src/themes/claude.ts | 118 ++ src/themes/cortex.ts | 94 ++ src/themes/cursor.ts | 94 ++ src/themes/dracula.ts | 63 + src/themes/everforest.ts | 116 ++ src/themes/gruvbox.ts | 116 ++ src/themes/index.ts | 49 + src/themes/kanagawa-dragon.ts | 63 + src/themes/kanagawa.ts | 116 ++ src/themes/nord.ts | 91 ++ src/themes/rose-pine.ts | 116 ++ src/themes/tide.ts | 118 ++ src/themes/tokyo-night.ts | 64 + 52 files changed, 5959 insertions(+), 2968 deletions(-) rename src/{types => lib}/index.ts (100%) rename src/{types => lib}/onboarding.ts (86%) create mode 100644 src/lib/theme-types.ts create mode 100644 src/themes/caffeine.ts create mode 100644 src/themes/catppuccin.ts create mode 100644 src/themes/claude.ts create mode 100644 src/themes/cortex.ts create mode 100644 src/themes/cursor.ts create mode 100644 src/themes/dracula.ts create mode 100644 src/themes/everforest.ts create mode 100644 src/themes/gruvbox.ts create mode 100644 src/themes/index.ts create mode 100644 src/themes/kanagawa-dragon.ts create mode 100644 src/themes/kanagawa.ts create mode 100644 src/themes/nord.ts create mode 100644 src/themes/rose-pine.ts create mode 100644 src/themes/tide.ts create mode 100644 src/themes/tokyo-night.ts diff --git a/src/App.tsx b/src/App.tsx index bf74229..1f5e0e2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { SpaceView } from "./features/space/SpaceView"; import { useTheme, ThemeName } from "./hooks/useTheme"; import { useColorScheme } from "./hooks/useColorScheme"; import { Toaster } from "@/components/ui/sonner"; -import { AppState } from "./types"; +import { AppState } from "./lib"; import { useWindowControls } from "./hooks/useWindowControls"; import { useAppShortcuts } from "./hooks/useAppShortcuts"; import { AppHeader } from "./components/layout/AppHeader"; @@ -26,22 +26,22 @@ import { WorkspaceProvider, useWorkspace } from "./context/WorkspaceContext"; const KeyboardShortcutsDialog = lazy(() => import("./components/dialogs/KeyboardShortcutsDialog").then((m) => ({ default: m.KeyboardShortcutsDialog, - })) + })), ); const SettingsDialog = lazy(() => import("./features/settings/SettingsDialog").then((m) => ({ default: m.SettingsDialog, - })) + })), ); const CortexLibraryDialog = lazy(() => import("./features/cortex-library/CortexLibraryDialog").then((m) => ({ default: m.CortexLibraryDialog, - })) + })), ); const WorkspaceSwitcherDialog = lazy(() => import("./components/dialogs/WorkspaceSwitcherDialog").then((m) => ({ default: m.WorkspaceSwitcherDialog, - })) + })), ); declare global { @@ -143,8 +143,11 @@ function AppInner() { toggleZenMode, resetToDefaults: resetFocus, } = useFocusSettings(); - const { settings: demoSettings, setDemoSetting, resetToDefaults: resetDemo } = - useDemoSettings(); + const { + settings: demoSettings, + setDemoSetting, + resetToDefaults: resetDemo, + } = useDemoSettings(); // ── Global event listeners ─────────────────────────────────────────────── useEffect(() => { @@ -166,7 +169,7 @@ function AppInner() { return () => { window.removeEventListener( "cortex:modal-depth-changed", - handleDepthChange + handleDepthChange, ); window.removeEventListener("keydown", handleGlobalKeyDown); }; @@ -272,12 +275,12 @@ function AppInner() { command: snippet.command, execute, }, - }) + }), ); setTemplatesOpen(false); setSwitcherOpen(false); }, - [activeWorkspaceId] + [activeWorkspaceId], ); // ── Render ─────────────────────────────────────────────────────────────── @@ -422,7 +425,9 @@ function AppInner() { isZenMode={focusSettings.isZenMode} setIsZenMode={(v) => setFocusSetting("isZenMode", v)} zenPadding={colorSchemeSettings.zenPadding} - showPaneHeaders={focusSettings.showPaneHeaders as boolean} + showPaneHeaders={ + focusSettings.showPaneHeaders as boolean + } onSplitPane={handleSplitPane} onMovePane={handleMovePane} onKillPane={handleKillPane} diff --git a/src/components/dialogs/WorkspaceSwitcherDialog.tsx b/src/components/dialogs/WorkspaceSwitcherDialog.tsx index 4243313..15c92ad 100644 --- a/src/components/dialogs/WorkspaceSwitcherDialog.tsx +++ b/src/components/dialogs/WorkspaceSwitcherDialog.tsx @@ -1,24 +1,44 @@ import { useState, useMemo, useEffect, useCallback } from "react"; -import { - Dialog, - DialogContent, -} from "@/components/ui/dialog"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { Workspace, SpaceTemplate, Snippet } from "@/types"; +import { Workspace, SpaceTemplate, Snippet } from "@/lib"; import { ThemeDefinition } from "@/hooks/useTheme"; import { LayoutPreviewIcon } from "@/components/ui/layout-preview-icon"; -import { Search, Folder, Terminal, Bot, Zap, Rocket, Settings, Keyboard, Maximize, Palette, ChevronRightSquare, Code, Play, Command, X } from "@/components/ui/icons"; +import { + Search, + Folder, + Terminal, + Bot, + Zap, + Rocket, + Settings, + Keyboard, + Maximize, + Palette, + ChevronRightSquare, + Code, + Play, + Command, + X, +} from "@/components/ui/icons"; import { cn } from "@/lib/utils"; import { Kbd, KbdGroup } from "@/components/ui/kbd"; import { parseShortcutToKeys } from "@/lib/shortcut-utils"; import { EmptyState } from "@/components/ui/empty-state"; -type PaletteItem = - | { type: 'workspace'; data: Workspace; shortcut?: string } - | { type: 'template'; data: SpaceTemplate; shortcut?: string } - | { type: 'action'; id: string; label: string; icon: any; action: () => void; shortcut?: string } - | { type: 'snippet'; data: Snippet; shortcut?: string }; +type PaletteItem = + | { type: "workspace"; data: Workspace; shortcut?: string } + | { type: "template"; data: SpaceTemplate; shortcut?: string } + | { + type: "action"; + id: string; + label: string; + icon: any; + action: () => void; + shortcut?: string; + } + | { type: "snippet"; data: Snippet; shortcut?: string }; interface WorkspaceSwitcherDialogProps { isOpen: boolean; @@ -53,50 +73,101 @@ export function WorkspaceSwitcherDialog({ onOpenSettings, onOpenShortcuts, onOpenTemplates, - onSetTheme + onSetTheme, }: WorkspaceSwitcherDialogProps) { const [searchQuery, setSearchQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); - const isMac = typeof window !== 'undefined' && navigator.userAgent.includes('Mac'); + const isMac = + typeof window !== "undefined" && navigator.userAgent.includes("Mac"); const modKey = isMac ? "⌘" : "Ctrl"; const actions = useMemo((): PaletteItem[] => { const baseActions: PaletteItem[] = [ - { type: 'action', id: 'toggle-zen', label: 'Toggle Zen Mode', icon: Maximize, action: onToggleZenMode, shortcut: `${modKey}+Shift+Z` }, - { type: 'action', id: 'open-settings', label: 'Open Preferences', icon: Settings, action: onOpenSettings, shortcut: `${modKey}+,` }, - { type: 'action', id: 'open-shortcuts', label: 'View Keyboard Shortcuts', icon: Keyboard, action: onOpenShortcuts, shortcut: `${modKey}+/` }, - { type: 'action', id: 'open-templates', label: 'Manage Cortex Library', icon: Rocket, action: onOpenTemplates, shortcut: `${modKey}+T` }, + { + type: "action", + id: "toggle-zen", + label: "Toggle Zen Mode", + icon: Maximize, + action: onToggleZenMode, + shortcut: `${modKey}+Shift+Z`, + }, + { + type: "action", + id: "open-settings", + label: "Open Preferences", + icon: Settings, + action: onOpenSettings, + shortcut: `${modKey}+,`, + }, + { + type: "action", + id: "open-shortcuts", + label: "View Keyboard Shortcuts", + icon: Keyboard, + action: onOpenShortcuts, + shortcut: `${modKey}+/`, + }, + { + type: "action", + id: "open-templates", + label: "Manage Cortex Library", + icon: Rocket, + action: onOpenTemplates, + shortcut: `${modKey}+T`, + }, ]; - const themeActions: PaletteItem[] = allThemes.map(t => ({ - type: 'action', + const themeActions: PaletteItem[] = allThemes.map((t) => ({ + type: "action", id: `theme-${t.id}`, label: `Theme: ${t.name}`, icon: Palette, - action: () => onSetTheme(t.id) + action: () => onSetTheme(t.id), })); return [...baseActions, ...themeActions]; - }, [allThemes, onToggleZenMode, onOpenSettings, onOpenShortcuts, onOpenTemplates, onSetTheme, modKey]); + }, [ + allThemes, + onToggleZenMode, + onOpenSettings, + onOpenShortcuts, + onOpenTemplates, + onSetTheme, + modKey, + ]); const filteredItems = useMemo(() => { const query = searchQuery.toLowerCase(); - + const wsItems: PaletteItem[] = workspaces - .filter(ws => (ws.name || "").toLowerCase().includes(query) || (ws.customName || "").toLowerCase().includes(query) || (ws.config?.rootPath || "").toLowerCase().includes(query)) - .map(ws => ({ type: 'workspace', data: ws })); + .filter( + (ws) => + (ws.name || "").toLowerCase().includes(query) || + (ws.customName || "").toLowerCase().includes(query) || + (ws.config?.rootPath || "").toLowerCase().includes(query), + ) + .map((ws) => ({ type: "workspace", data: ws })); const templateItems: PaletteItem[] = templates - .filter(t => t.name.toLowerCase().includes(query) || (t.description || "").toLowerCase().includes(query) || t.rootPath.toLowerCase().includes(query)) - .map(t => ({ type: 'template', data: t, shortcut: `${modKey}+T` })); + .filter( + (t) => + t.name.toLowerCase().includes(query) || + (t.description || "").toLowerCase().includes(query) || + t.rootPath.toLowerCase().includes(query), + ) + .map((t) => ({ type: "template", data: t, shortcut: `${modKey}+T` })); const snippetItems: PaletteItem[] = snippets - .filter(s => s.label.toLowerCase().includes(query) || s.command.toLowerCase().includes(query)) - .map(s => ({ type: 'snippet', data: s, shortcut: `Shift+Enter` })); + .filter( + (s) => + s.label.toLowerCase().includes(query) || + s.command.toLowerCase().includes(query), + ) + .map((s) => ({ type: "snippet", data: s, shortcut: `Shift+Enter` })); - const actionItems: PaletteItem[] = actions.filter(a => - a.type === 'action' && a.label.toLowerCase().includes(query) + const actionItems: PaletteItem[] = actions.filter( + (a) => a.type === "action" && a.label.toLowerCase().includes(query), ); return [...wsItems, ...templateItems, ...snippetItems, ...actionItems]; @@ -107,13 +178,17 @@ export function WorkspaceSwitcherDialog({ setSelectedIndex(0); }, [searchQuery]); - const handleExecuteItem = useCallback((item: PaletteItem, isShiftPressed: boolean = false) => { - if (item.type === 'workspace') onSwitchWorkspace(item.data.id); - else if (item.type === 'template') onLaunchTemplate(item.data); - else if (item.type === 'snippet') onSnippetExecute(item.data, isShiftPressed); - else if (item.type === 'action') item.action(); - onOpenChange(false); - }, [onSwitchWorkspace, onLaunchTemplate, onSnippetExecute, onOpenChange]); + const handleExecuteItem = useCallback( + (item: PaletteItem, isShiftPressed: boolean = false) => { + if (item.type === "workspace") onSwitchWorkspace(item.data.id); + else if (item.type === "template") onLaunchTemplate(item.data); + else if (item.type === "snippet") + onSnippetExecute(item.data, isShiftPressed); + else if (item.type === "action") item.action(); + onOpenChange(false); + }, + [onSwitchWorkspace, onLaunchTemplate, onSnippetExecute, onOpenChange], + ); // Keyboard navigation useEffect(() => { @@ -122,10 +197,13 @@ export function WorkspaceSwitcherDialog({ const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "ArrowDown") { e.preventDefault(); - setSelectedIndex(prev => (prev + 1) % (filteredItems.length || 1)); + setSelectedIndex((prev) => (prev + 1) % (filteredItems.length || 1)); } else if (e.key === "ArrowUp") { e.preventDefault(); - setSelectedIndex(prev => (prev - 1 + filteredItems.length) % (filteredItems.length || 1)); + setSelectedIndex( + (prev) => + (prev - 1 + filteredItems.length) % (filteredItems.length || 1), + ); } else if (e.key === "Enter") { e.preventDefault(); const selected = filteredItems[selectedIndex]; @@ -149,7 +227,7 @@ export function WorkspaceSwitcherDialog({ return ( -
- - + setSearchQuery(e.target.value)} @@ -184,7 +261,9 @@ export function WorkspaceSwitcherDialog({ )}
- ESC + + ESC +
@@ -192,7 +271,7 @@ export function WorkspaceSwitcherDialog({
{filteredItems.length === 0 ? ( - { const isSelected = selectedIndex === index; - + return (
setSelectedIndex(index)} className={cn( "group flex items-center justify-between p-3 rounded-lg cursor-pointer transition-all duration-150", - isSelected ? "bg-[var(--text-primary)]/[0.07]" : "hover:bg-[var(--text-primary)]/[0.03]" + isSelected + ? "bg-[var(--text-primary)]/[0.07]" + : "hover:bg-[var(--text-primary)]/[0.03]", )} >
- {item.type === 'workspace' && ( + {item.type === "workspace" && (
{item.data.config?.layout ? ( - + ) : ( -
- {item.data.mode === 'agents' ? : } -
+
+ {item.data.mode === "agents" ? ( + + ) : ( + + )} +
)} {activeWorkspaceId === item.data.id && ( -
+
)}
)} - {item.type === 'template' && ( - )} - {item.type === 'snippet' && ( -
- + {item.type === "snippet" && ( +
+
)} - {item.type === 'action' && ( -
- + {item.type === "action" && ( +
+
)}
- - {item.type === 'workspace' ? (item.data.customName || item.data.name || "Unnamed Workspace") : - item.type === 'template' ? item.data.name : - item.type === 'snippet' ? item.data.label : - item.label} + + {item.type === "workspace" + ? item.data.customName || + item.data.name || + "Unnamed Workspace" + : item.type === "template" + ? item.data.name + : item.type === "snippet" + ? item.data.label + : item.label} - - {item.type.charAt(0).toUpperCase() + item.type.slice(1)} + + {item.type.charAt(0).toUpperCase() + + item.type.slice(1)}
- {item.type === 'workspace' && ( - <> {item.data.config?.rootPath || "No directory selected"} + {item.type === "workspace" && ( + <> + {" "} + + {item.data.config?.rootPath || + "No directory selected"} + + )} - {item.type === 'template' && ( - <> Launch New Instance + {item.type === "template" && ( + <> + {" "} + + Launch New Instance + + )} - {item.type === 'snippet' && ( - <> {item.data.command} + {item.type === "snippet" && ( + <> + {" "} + + {item.data.command} + + )} - {item.type === 'action' && ( - <> System Command + {item.type === "action" && ( + <> + {" "} + + System Command + + )}
@@ -299,31 +452,48 @@ export function WorkspaceSwitcherDialog({
{isSelected ? (
- {item.type === 'snippet' && ( - + {item.type === "snippet" && ( + )}
- {modKey} - {item.type === 'workspace' ? 'Switch' : item.type === 'template' ? 'Launch' : item.type === 'snippet' ? 'Inject' : 'Execute'} + + {modKey} + + + {item.type === "workspace" + ? "Switch" + : item.type === "template" + ? "Launch" + : item.type === "snippet" + ? "Inject" + : "Execute"} +
- ) : item.shortcut && ( - - {parseShortcutToKeys(item.shortcut, isMac).map((key, idx) => ( - - {key} - - ))} - + ) : ( + item.shortcut && ( + + {parseShortcutToKeys(item.shortcut, isMac).map( + (key, idx) => ( + + {key} + + ), + )} + + ) )}
@@ -336,17 +506,24 @@ export function WorkspaceSwitcherDialog({
- ↑↓ + + ↑↓ + Navigate
- Enter + + Enter + Select
{parseShortcutToKeys("Shift+Enter", false).map((key, idx) => ( - + {key} ))} diff --git a/src/components/layout/AppHeader.tsx b/src/components/layout/AppHeader.tsx index 69996e1..99936c2 100644 --- a/src/components/layout/AppHeader.tsx +++ b/src/components/layout/AppHeader.tsx @@ -21,7 +21,7 @@ import { } from "@/components/ui/icons"; import { Reorder } from "framer-motion"; import { Button } from "@/components/ui/button"; -import { Workspace } from "@/types"; +import { Workspace } from "@/lib"; import { InteractiveTab, COLOR_MAP, diff --git a/src/components/screens/FirstRunOnboardingScreen.tsx b/src/components/screens/FirstRunOnboardingScreen.tsx index 70d9c56..4fb52de 100644 --- a/src/components/screens/FirstRunOnboardingScreen.tsx +++ b/src/components/screens/FirstRunOnboardingScreen.tsx @@ -6,7 +6,7 @@ import { getSetting, setSetting } from '@/lib/store'; import { Button } from '@/components/ui/button'; import { useAgents } from '@/hooks/useAgents'; import { useTheme } from '@/hooks/useTheme'; -import type { Agent } from '@/types'; +import type { Agent } from '@/lib'; import { Loader2, ArrowRight } from '@/components/ui/icons'; import type { @@ -15,8 +15,8 @@ import type { InstallableTool, PathValidationState, WorkspacePathValidation, -} from '@/types/onboarding'; -import { INITIAL_BOOT_CHECKS, PROFILES } from '@/types/onboarding'; +} from '@/lib/onboarding'; +import { INITIAL_BOOT_CHECKS, PROFILES } from '@/lib/onboarding'; import { StepFoundation } from './onboarding/StepFoundation'; import { StepChoice } from './onboarding/StepChoice'; @@ -62,7 +62,7 @@ export const FirstRunOnboardingScreen = memo(function FirstRunOnboardingScreen({ ]); // Option A configuration states - const [selectedProfile, setSelectedProfile] = useState<'zen' | 'intelligence' | 'pro' | null>(null); + const [selectedProfile, setSelectedProfile] = useState<'zen' | 'intelligence' | 'pro' | 'creator' | null>(null); const [proShellPreference, setProShellPreference] = useState('powershell.exe'); // Option B configuration states @@ -409,7 +409,7 @@ export const FirstRunOnboardingScreen = memo(function FirstRunOnboardingScreen({ themeName = customTheme; layout = customLayout; shell = customShell; - + await setSetting('terminal.fontSize', customFontSize); await setSetting('terminal.fontFamily', customFontFamily); } @@ -599,8 +599,8 @@ export const FirstRunOnboardingScreen = memo(function FirstRunOnboardingScreen({
{/* ── Main content area ───────────────────────────────── */} -
-
+
+
{ - const shouldReduceMotion = useReducedMotion(); - const [lastMode, setLastMode] = useState(null); - const isMac = typeof window !== 'undefined' && navigator.userAgent.includes('Mac'); +export const ModeSelectorScreen = React.memo( + ({ + onSelectMode, + onBack, + showShortcutHints = true, + showTemplatesHint = true, + }: ModeSelectorScreenProps) => { + const shouldReduceMotion = useReducedMotion(); + const [lastMode, setLastMode] = useState(null); + const isMac = + typeof window !== "undefined" && navigator.userAgent.includes("Mac"); - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - e.preventDefault(); - onBack?.(); - } - }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [onBack]); - - useEffect(() => { - getSetting("startup.lastMode", "normal").then(setLastMode); - }, []); - - const handleSelectMode = async (mode: Mode) => { - await setSetting("startup.lastMode", mode); - onSelectMode(mode); - }; - - const containerVariants: Variants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { - staggerChildren: 0.1, - delayChildren: 0.1 - } - } - }; + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault(); + onBack?.(); + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [onBack]); - const itemVariants: Variants = { - hidden: { - opacity: 0, - y: shouldReduceMotion ? 0 : 20, - }, - visible: { - opacity: 1, - y: 0, - transition: { - duration: 0.8, - ease: [0.22, 1, 0.36, 1] as any - } - } - }; + useEffect(() => { + getSetting("startup.lastMode", "normal").then(setLastMode); + }, []); - return ( -
- {/* Background ambient detail - Asymmetric glow */} -
-
-
-
-
+ const handleSelectMode = async (mode: Mode) => { + await setSetting("startup.lastMode", mode); + onSelectMode(mode); + }; - - {/* Left Column: Brand & Intro (Asymmetric focus) */} -
- -
- Cortex Logo { - e.currentTarget.src = ASSETS.LOGO_FALLBACK; - }} - /> - + const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.1, + }, + }, + }; -
- -

- {MODE_SELECTOR_CONTENT.TITLE}
- {MODE_SELECTOR_CONTENT.SUBTITLE} -

-

- {MODE_SELECTOR_CONTENT.DESCRIPTION} -

-
+ const itemVariants: Variants = { + hidden: { + opacity: 0, + y: shouldReduceMotion ? 0 : 20, + }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.8, + ease: [0.22, 1, 0.36, 1] as any, + }, + }, + }; - -
+ return ( +
+ {/* Background ambient detail - Asymmetric glow */} +
+
+
+
+
- {showShortcutHints && ( + + {/* Left Column: Brand & Intro (Asymmetric focus) */} +
-

- Quick Navigation -

-
- {showTemplatesHint && ( +
+ Cortex Logo { + e.currentTarget.src = ASSETS.LOGO_FALLBACK; + }} + /> + + +
+ +

+ {MODE_SELECTOR_CONTENT.TITLE} +
+ + {" "} + {MODE_SELECTOR_CONTENT.SUBTITLE} + +

+

+ {MODE_SELECTOR_CONTENT.DESCRIPTION} +

+
+ + +
+ + {showShortcutHints && ( + +

+ Quick Navigation +

+
+ {showTemplatesHint && ( +
+ + {parseShortcutToKeys("Ctrl+Shift+T", isMac).map( + (key, idx) => ( + + {key} + + ), + )} + + + {MODE_SELECTOR_CONTENT.HINTS.TEMPLATES} + +
+ )}
- {parseShortcutToKeys("Ctrl+Shift+T", isMac).map((key, idx) => ( - + {parseShortcutToKeys("Ctrl+T", isMac).map((key, idx) => ( + {key} ))} - {MODE_SELECTOR_CONTENT.HINTS.TEMPLATES} + + {MODE_SELECTOR_CONTENT.HINTS.NEW_SPACE} + +
+
+ + {parseShortcutToKeys("Ctrl+,", isMac).map((key, idx) => ( + + {key} + + ))} + + + {MODE_SELECTOR_CONTENT.HINTS.SETTINGS} +
- )} -
- - {parseShortcutToKeys("Ctrl+T", isMac).map((key, idx) => ( - - {key} - - ))} - - {MODE_SELECTOR_CONTENT.HINTS.NEW_SPACE} -
-
- - {parseShortcutToKeys("Ctrl+,", isMac).map((key, idx) => ( - - {key} - - ))} - - {MODE_SELECTOR_CONTENT.HINTS.SETTINGS}
-
-
- )} -
+ + )} +
- {/* Right Column: Mode Selection Cards (Staggered & Offset) */} -
- {/* Subtle connecting line or graphic could go here */} + {/* Right Column: Mode Selection Cards (Staggered & Offset) */} +
+ {/* Subtle connecting line or graphic could go here */} - handleSelectMode("normal")} - className="transform md:translate-x-4" - > - handleSelectMode("normal")} + className="transform md:translate-x-4" > -
-
- {lastMode === "normal" && ( - - Last Session - - )} -
+ +
+
+ {lastMode === "normal" && ( + + Last Session + + )} +
-
- - - -
+
+ + + +
-
-
-

- {MODE_SELECTOR_CONTENT.NORMAL_MODE.TITLE} -

- {showShortcutHints && ( - - {parseShortcutToKeys("Ctrl+N", isMac).map((key, idx) => ( - - {key} - - ))} - - )} +
+
+

+ {MODE_SELECTOR_CONTENT.NORMAL_MODE.TITLE} +

+ {showShortcutHints && ( + + {parseShortcutToKeys("Ctrl+N", isMac).map( + (key, idx) => ( + + {key} + + ), + )} + + )} +
+

+ {MODE_SELECTOR_CONTENT.NORMAL_MODE.DESCRIPTION} +

-

- {MODE_SELECTOR_CONTENT.NORMAL_MODE.DESCRIPTION} -

-
- - + + - handleSelectMode("agents")} - className="transform md:-translate-x-4" - > - handleSelectMode("agents")} + className="transform md:-translate-x-4" > -
-
- {lastMode === "agents" && ( - - Last Session - - )} -
+ +
+
+ {lastMode === "agents" && ( + + Last Session + + )} +
-
- - - - - - -
+
+ + + + + + +
-
-
-

- {MODE_SELECTOR_CONTENT.AGENTS_MODE.TITLE} -

- {showShortcutHints && ( - - {parseShortcutToKeys("Ctrl+A", isMac).map((key, idx) => ( - - {key} - - ))} - - )} +
+
+

+ {MODE_SELECTOR_CONTENT.AGENTS_MODE.TITLE} +

+ {showShortcutHints && ( + + {parseShortcutToKeys("Ctrl+A", isMac).map( + (key, idx) => ( + + {key} + + ), + )} + + )} +
+

+ {MODE_SELECTOR_CONTENT.AGENTS_MODE.DESCRIPTION} +

-

- {MODE_SELECTOR_CONTENT.AGENTS_MODE.DESCRIPTION} -

-
- - -
- -
- ); -}); +
+
+
+ +
+ ); + }, +); diff --git a/src/components/screens/onboarding/BootLog.tsx b/src/components/screens/onboarding/BootLog.tsx index d33f038..aa5acfc 100644 --- a/src/components/screens/onboarding/BootLog.tsx +++ b/src/components/screens/onboarding/BootLog.tsx @@ -1,42 +1,45 @@ -import { memo } from 'react'; -import type { SysCheck, CheckStatus } from '@/types/onboarding'; +import { memo } from "react"; +import type { SysCheck, CheckStatus } from "@/lib/onboarding"; -export const BootLog = memo(function BootLog({ +export const BootLog = memo(function BootLog({ checks, finished, -}: { +}: { checks: SysCheck[]; finished: boolean; }) { const statusColor: Record = { - pending: 'var(--text-secondary)', - checking: 'var(--accent-primary)', - ok: 'var(--ansi-green, #10B981)', - warn: 'var(--ansi-yellow, #F59E0B)', - fail: 'var(--ansi-red, #EF4444)', + pending: "var(--text-secondary)", + checking: "var(--accent-primary)", + ok: "var(--ansi-green, #10B981)", + warn: "var(--ansi-yellow, #F59E0B)", + fail: "var(--ansi-red, #EF4444)", }; return (
{checks.map((check) => (
> {check.label}... - {check.status === 'checking' && ( + {check.status === "checking" && ( _ diff --git a/src/components/screens/onboarding/StepActivation.tsx b/src/components/screens/onboarding/StepActivation.tsx index 4f34cb8..125746f 100644 --- a/src/components/screens/onboarding/StepActivation.tsx +++ b/src/components/screens/onboarding/StepActivation.tsx @@ -1,8 +1,9 @@ import { useState, useMemo } from 'react'; import { m, AnimatePresence } from 'framer-motion'; import { ShieldCheck, Settings, ChevronDown, ChevronRight } from '@/components/ui/icons'; -import type { Agent } from '@/types'; -import type { FlowMode, Profile } from '@/types/onboarding'; +import { Kbd, KbdGroup } from '@/components/ui/kbd'; +import type { Agent } from '@/lib'; +import type { FlowMode, Profile } from '@/lib/onboarding'; import { LayoutThumbnail } from './StepPickProfile'; // Step: Activation (Final Review — used by both flows) @@ -55,7 +56,7 @@ export function StepActivation({ {flowMode === 'starter' ? 'Configuration Receipt' : 'Configuration Summary'}

- {flowMode === 'starter' + {flowMode === 'starter' ? 'Review your starter pack details below. Expand the details block if you want to inspect raw variables.' : 'Verify your custom configuration details below. Your workspace will launch directly with these parameters.'}

@@ -66,14 +67,14 @@ export function StepActivation({ {flowMode === 'starter' && profile ? ( /* Scannable 3-column receipt (Theme, Agents, Layout) */
- + {/* Column 1: Theme */}
01 / Visual Theme
-
@@ -187,7 +188,7 @@ export function StepActivation({ )}
- Your settings are stored locally in settings.json. You can alter these parameters at any time by triggering the global configuration panel (Cmd+, or Ctrl+,). + Your settings are stored locally in settings.json. You can alter these parameters at any time by triggering the global configuration panel (Cmd, or Ctrl,).
); diff --git a/src/components/screens/onboarding/StepChoice.tsx b/src/components/screens/onboarding/StepChoice.tsx index 34acc81..2a5dc08 100644 --- a/src/components/screens/onboarding/StepChoice.tsx +++ b/src/components/screens/onboarding/StepChoice.tsx @@ -1,8 +1,6 @@ -import { m } from 'framer-motion'; -import { - Zap, Settings, ArrowRight, AlertCircle -} from '@/components/ui/icons'; -import type { FlowMode } from '@/types/onboarding'; +import { m } from "framer-motion"; +import { Zap, Settings, ArrowRight, AlertCircle } from "@/components/ui/icons"; +import type { FlowMode } from "@/lib/onboarding"; // Step 2: Configuration Choice (Starter Profiles vs Custom Setup) export function StepChoice({ @@ -24,7 +22,8 @@ export function StepChoice({ Choose Setup Strategy

- Opt for pre-engineered settings profiles for instant startup, or custom-tune every detail of your environment. + Opt for pre-engineered settings profiles for instant startup, or + custom-tune every detail of your environment.

@@ -33,18 +32,23 @@ export function StepChoice({
{/* Starter Profiles Option */} { - if (!disabled) onSelect('starter'); + if (!disabled) onSelect("starter"); }} - className={`group rounded-xl border border-[var(--border-color)] bg-[var(--surface-color)]/30 p-6 flex flex-col justify-between transition-all duration-300 shadow-lg text-left ${ + className={`cursor-pointer rounded-xl border p-5 transition-all duration-300 flex flex-col justify-between bg-[var(--surface-color)]/20 relative overflow-hidden group text-left ${ disabled - ? 'cursor-not-allowed opacity-45' - : 'cursor-pointer hover:bg-[var(--surface-color)]/80 hover:border-[var(--accent-primary)]/40' + ? "cursor-not-allowed opacity-45 border-[var(--border-color)]/40" + : "border-[var(--border-color)]/40 hover:bg-[var(--surface-color)]/45" }`} > -
+ {/* Accent glow on hover */} + {!disabled && ( +
+ )} + +
@@ -53,29 +57,35 @@ export function StepChoice({ Option A: Starter Profiles

- Choose a pre-configured template (Zen, AI-First, or Power User) to immediately launch with customized settings. + Choose a pre-configured template (Zen, AI-First, or Power User) + to immediately launch with customized settings.

-
+
Select Profiles
{/* Custom Setup Option */} { - if (!disabled) onSelect('custom'); + if (!disabled) onSelect("custom"); }} - className={`group rounded-xl border border-[var(--border-color)] bg-[var(--surface-color)]/30 p-6 flex flex-col justify-between transition-all duration-300 shadow-lg text-left ${ + className={`cursor-pointer rounded-xl border p-5 transition-all duration-300 flex flex-col justify-between bg-[var(--surface-color)]/20 relative overflow-hidden group text-left ${ disabled - ? 'cursor-not-allowed opacity-45' - : 'cursor-pointer hover:bg-[var(--surface-color)]/80 hover:border-[var(--accent-primary)]/40' + ? "cursor-not-allowed opacity-45 border-[var(--border-color)]/40" + : "border-[var(--border-color)]/40 hover:bg-[var(--surface-color)]/45" }`} > -
+ {/* Accent glow on hover */} + {!disabled && ( +
+ )} + +
@@ -84,11 +94,12 @@ export function StepChoice({ Option B: Custom Setup

- Manually configure shell executables, download intelligence agents, switch visual themes, and adjust typography sizing. + Manually configure shell executables, download intelligence + agents, switch visual themes, and adjust typography sizing.

-
+
Customize Environment
@@ -96,8 +107,13 @@ export function StepChoice({ {disabled && disabledReason && (
- - {disabledReason} + + + {disabledReason} +
)}
diff --git a/src/components/screens/onboarding/StepFoundation.tsx b/src/components/screens/onboarding/StepFoundation.tsx index 874eb32..7ebf645 100644 --- a/src/components/screens/onboarding/StepFoundation.tsx +++ b/src/components/screens/onboarding/StepFoundation.tsx @@ -1,17 +1,28 @@ -import { useState, useCallback } from 'react'; -import { m, AnimatePresence } from 'framer-motion'; -import { open } from '@tauri-apps/plugin-dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; +import { useState, useCallback } from "react"; +import { m, AnimatePresence } from "framer-motion"; +import { open } from "@tauri-apps/plugin-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { - Loader2, Download, CheckCircle2, - FolderOpen, AlertCircle, Settings, - ChevronDown, ChevronRight -} from '@/components/ui/icons'; -import type { SysCheck, PathValidationState, InstallableTool } from '@/types/onboarding'; -import { isInstallableTool } from '@/types/onboarding'; -import { ScrambleText } from './ScrambleText'; -import { BootLog } from './BootLog'; + Loader2, + Download, + CheckCircle2, + FolderOpen, + AlertCircle, + Settings, + ChevronDown, + ChevronRight, + X, +} from "@/components/ui/icons"; +import { Spotlight } from "@/components/ui/spotlight"; +import type { + SysCheck, + PathValidationState, + InstallableTool, +} from "@/lib/onboarding"; +import { isInstallableTool } from "@/lib/onboarding"; +import { ScrambleText } from "./ScrambleText"; +import { BootLog } from "./BootLog"; // Step 1: Foundation (Welcome + Boot Sequence + Workspace Root + Shell & Diagnostics) export function StepFoundation({ @@ -43,20 +54,20 @@ export function StepFoundation({ installingTools: Partial>; onInstallTool: (tool: InstallableTool) => void; }) { - const [browseError, setBrowseError] = useState(''); + const [browseError, setBrowseError] = useState(""); const [isAdvancedOpen, setIsAdvancedOpen] = useState(false); const handleBrowse = useCallback(async () => { - setBrowseError(''); + setBrowseError(""); try { const selected = await open({ directory: true, multiple: false, - title: 'Select workspace root directory', + title: "Select workspace root directory", }); if (selected) setPath(selected as string); } catch { - setBrowseError('Directory picker unavailable'); + setBrowseError("Directory picker unavailable"); } }, [setPath]); @@ -68,13 +79,18 @@ export function StepFoundation({

{skip ? ( - 'THE COMMAND CENTER' + "THE COMMAND CENTER" ) : ( - + )}

- Welcome to your unified workspace. Let's perform initial diagnostics and verify your environment path. + Welcome to your unified workspace. Let's perform initial diagnostics + and verify your environment path.

@@ -91,9 +107,13 @@ export function StepFoundation({ {bootFailed && (
- + - Cortex could not complete startup checks. Resolve the failed item above before continuing. + Cortex could not complete startup checks. Resolve the failed item + above before continuing.
)} @@ -103,68 +123,88 @@ export function StepFoundation({
- -
-
- - ~/ - +
+ +
+ +
+ { setPath(e.target.value); - setBrowseError(''); + setBrowseError(""); }} - placeholder="workspace" - aria-invalid={pathValidation.status === 'invalid'} - className={`pl-8 font-mono text-xs h-9 bg-[var(--surface-color)] ${ - pathValidation.status === 'invalid' - ? 'border-[var(--ansi-red,#EF4444)]' - : pathValidation.status === 'valid' - ? 'border-[var(--ansi-green,#10B981)]/70' - : 'border-[var(--border-color)]' - }`} + placeholder="Select a workspace folder" + autoComplete="off" + className="h-8 border-none bg-transparent px-0.5 font-mono text-xs text-[var(--text-primary)] shadow-none focus-visible:ring-0 placeholder:text-[var(--text-secondary)]/10 font-bold" />
- -
+
+ {path && ( + + )} +
+ +
+ {browseError && ( {browseError} )} - {pathValidation.status !== 'idle' && ( + {pathValidation.status !== "idle" && ( - {pathValidation.status === 'checking' && } - {pathValidation.status === 'valid' && } - {pathValidation.status === 'invalid' && } + {pathValidation.status === "checking" && ( + + )} + {pathValidation.status === "valid" && ( + + )} + {pathValidation.status === "invalid" && ( + + )} {pathValidation.message} )} @@ -176,7 +216,9 @@ export function StepFoundation({

- Workspace path directs your default working directory for code sessions and agent triggers. Relative values resolve inside your home directory; leave blank to use home. + Workspace path directs your default working directory for code + sessions and agent triggers. Relative values resolve inside your + home directory; leave blank to use home.

@@ -189,25 +231,39 @@ export function StepFoundation({ className="w-full flex items-center justify-between p-3.5 font-bold text-xs uppercase tracking-wider text-[var(--text-primary)] hover:bg-[var(--surface-color)]/40 transition-colors select-none" >
- + Advanced System Configurations
- {isAdvancedOpen ? : } + {isAdvancedOpen ? ( + + ) : ( + + )} {isAdvancedOpen && (
{/* Shell Selector */}
-
{/* Scan section */}
- System Diagnostics + + System Diagnostics +
{checks.map((check) => ( -
+
- {check.label} - {check.detail || check.description} + + {check.label} + + + {check.detail || check.description} +
- {isInstallableTool(check.id) && check.status === 'fail' && ( - - )} - {check.status === 'checking' && ( + {isInstallableTool(check.id) && + check.status === "fail" && ( + + )} + {check.status === "checking" && ( )} - {check.status === 'ok' &&
} - {check.status === 'warn' &&
} - {check.status === 'fail' &&
} - + {check.status === "ok" && ( +
+ )} + {check.status === "warn" && ( +
+ )} + {check.status === "fail" && ( +
+ )} + {check.status}
diff --git a/src/components/screens/onboarding/StepPickProfile.tsx b/src/components/screens/onboarding/StepPickProfile.tsx index 811ea1a..0d61e9b 100644 --- a/src/components/screens/onboarding/StepPickProfile.tsx +++ b/src/components/screens/onboarding/StepPickProfile.tsx @@ -1,12 +1,12 @@ -import { m } from 'framer-motion'; -import { Input } from '@/components/ui/input'; -import { Cpu, Check } from '@/components/ui/icons'; -import { PROFILES } from '@/types/onboarding'; +import { m } from "framer-motion"; +import { Input } from "@/components/ui/input"; +import { Cpu, Check } from "@/components/ui/icons"; +import { PROFILES } from "@/lib/onboarding"; // ── Layout Thumbnail ────────────────────────────────────────────────────────── export const LayoutThumbnail = ({ type }: { type: string }) => { - if (type.includes('Grid') || type.includes('2x2')) { + if (type.includes("Grid") || type.includes("2x2")) { return (
@@ -16,7 +16,7 @@ export const LayoutThumbnail = ({ type }: { type: string }) => {
); } - if (type.includes('1x3')) { + if (type.includes("1x3")) { return (
@@ -41,8 +41,8 @@ export function StepPickProfile({ proShell, setProShell, }: { - selected: 'zen' | 'intelligence' | 'pro' | null; - onSelect: (id: 'zen' | 'intelligence' | 'pro') => void; + selected: "zen" | "intelligence" | "pro" | "creator" | null; + onSelect: (id: "zen" | "intelligence" | "pro" | "creator") => void; proShell: string; setProShell: (v: string) => void; }) { @@ -56,7 +56,8 @@ export function StepPickProfile({ Select Starter Profile

- Choose a pre-configured template tailored to your workflow. Hover to inspect details. + Choose a pre-configured template tailored to your workflow. Hover to + inspect details.

@@ -73,12 +74,14 @@ export function StepPickProfile({ onClick={() => onSelect(profile.id)} className={`cursor-pointer rounded-xl border p-5 transition-all duration-300 flex flex-col gap-4 bg-[var(--surface-color)]/20 relative overflow-hidden group ${ isSelected - ? 'bg-[var(--surface-color)]/60' - : 'border-[var(--border-color)]/40 hover:bg-[var(--surface-color)]/45' + ? "bg-[var(--surface-color)]/60" + : "border-[var(--border-color)]/40 hover:bg-[var(--surface-color)]/45" }`} style={{ borderColor: isSelected ? profile.color : undefined, - boxShadow: isSelected ? `0 0 16px ${profile.color}15` : undefined, + boxShadow: isSelected + ? `0 0 16px ${profile.color}15` + : undefined, }} > {/* Accent glow on hover */} @@ -93,12 +96,12 @@ export function StepPickProfile({ {profile.name} - {profile.badge} @@ -110,14 +113,23 @@ export function StepPickProfile({
{/* Radio indicator */} -
- {isSelected && } + {isSelected && ( + + )}
@@ -125,13 +137,17 @@ export function StepPickProfile({
{/* Theme Info with Swatch */}
-
- Theme Visuals - {profile.themeName} + + Theme Visuals + + + {profile.themeName} +
@@ -139,20 +155,31 @@ export function StepPickProfile({
- Window Grid - {profile.layoutName} + + Window Grid + + + {profile.layoutName} +
{/* Included Agents info */}
- +
- Preinstalled Agents + + Preinstalled Agents + - {profile.includedAgentLabels.length > 0 ? profile.includedAgentLabels.join(', ') : 'None'} + {profile.includedAgentLabels.length > 0 + ? profile.includedAgentLabels.join(", ") + : "None"}
@@ -160,14 +187,20 @@ export function StepPickProfile({ {/* Custom shell dropdown for power users */} - {profile.id === 'pro' && isSelected && ( + {profile.id === "pro" && isSelected && ( -
-