diff --git a/frontend/public/onboarding-videos/v2/02.mp4 b/frontend/public/onboarding-videos/v2/02.mp4 deleted file mode 100644 index 36a79e31..00000000 Binary files a/frontend/public/onboarding-videos/v2/02.mp4 and /dev/null differ diff --git a/frontend/public/onboarding-videos/v2/07.mp4 b/frontend/public/onboarding-videos/v2/07.mp4 deleted file mode 100644 index fdadb924..00000000 Binary files a/frontend/public/onboarding-videos/v2/07.mp4 and /dev/null differ diff --git a/frontend/src/app/Main.tsx b/frontend/src/app/Main.tsx index 2016bac8..77f273be 100644 --- a/frontend/src/app/Main.tsx +++ b/frontend/src/app/Main.tsx @@ -27,10 +27,6 @@ import DashboardSelection from './pages/DashboardSelection/DashboardSelection'; import ErrorBoundary from './components/feedback/ErrorBoundary'; import { setPanelMode, disableOnboardingAfterCrash } from '@/shared/state/onboardingProgressSlice'; -const Skills = React.lazy(() => import('./pages/Skills/Skills')); -const Tools = React.lazy(() => import('./pages/Tools/Tools')); -const Modes = React.lazy(() => import('./pages/Modes/Modes')); -const Customization = React.lazy(() => import('./pages/Customization/Customization')); const Analytics = React.lazy(() => import('./pages/Analytics/Analytics')); const OnboardingRoot = React.lazy(() => import('./components/Onboarding').then((m) => ({ default: m.OnboardingRoot })), @@ -54,20 +50,11 @@ if (typeof window !== 'undefined') { (window as any).__openswarmPrefetchRoute = (path: string) => { switch (path) { - case '/skills': void import('./pages/Skills/Skills'); return; - case '/actions': - case '/tools': void import('./pages/Tools/Tools'); return; - case '/modes': void import('./pages/Modes/Modes'); return; case '/views': - case '/customization': void import('./pages/Customization/Customization'); return; case '/analytics': void import('./pages/Analytics/Analytics'); return; } }; const prefetchAll = () => { - void import('./pages/Skills/Skills'); - void import('./pages/Tools/Tools'); - void import('./pages/Modes/Modes'); - void import('./pages/Customization/Customization'); void import('./pages/Analytics/Analytics'); }; const ric = (window as any).requestIdleCallback as @@ -528,10 +515,6 @@ const ThemedApp: React.FC = () => { } /> {/* Dashboard renders persistently in AppShell so webviews survive nav. */} - } /> - } /> - } /> - } /> } /> diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx index f20d7be9..2b47615c 100644 --- a/frontend/src/app/components/Layout/AppShell.tsx +++ b/frontend/src/app/components/Layout/AppShell.tsx @@ -18,12 +18,9 @@ import Alert from '@mui/material/Alert'; import InputBase from '@mui/material/InputBase'; // One outlined icon language for the sidebar: thin monoline glyphs (not the filled Material clip-art) so the rail reads as designed, not assembled. import { LayoutDashboard } from 'lucide-react'; -import PsychologyIcon from '@mui/icons-material/PsychologyOutlined'; -import BuildIcon from '@mui/icons-material/BuildOutlined'; import { LayoutGrid } from 'lucide-react'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { Settings as LucideSettings } from 'lucide-react'; -import { Palette } from 'lucide-react'; import { ArrowLeft, ArrowRight, Plus, Clock } from 'lucide-react'; import { AnimatedPanelLeft } from './animatedIcons'; import RestartAltIcon from '@mui/icons-material/RestartAlt'; @@ -59,13 +56,6 @@ const SIDEBAR_DEFAULT = 260; const SIDEBAR_WIDTH_KEY = 'openswarm-sidebar-width'; const UPDATE_DISMISS_KEY = 'openswarm-update-dismissed'; -const CUSTOMIZATION_ITEMS = [ - { label: 'Skills', path: '/skills', icon: , onboarding: 'sidebar-skills' }, - { label: 'Actions', path: '/actions', icon: , onboarding: 'sidebar-actions' }, -]; - -const CUSTOMIZATION_PATHS = new Set(CUSTOMIZATION_ITEMS.map((i) => i.path)); - const AppShell: React.FC = () => { const c = useClaudeTokens(); const dispatch = useAppDispatch(); @@ -88,8 +78,6 @@ const AppShell: React.FC = () => { const canGoForward = historyIdx < maxHistoryIdx.current; const [dashboardsExpanded, setDashboardsExpanded] = useState(true); const [appsExpanded, setAppsExpanded] = useState(true); - // Collapsed by default: config rows are progressive disclosure, not daily nav. Onboarding reads data-expanded and clicks to open when it needs them. - const [customizationExpanded, setCustomizationExpanded] = useState(false); // Starts collapsed so a fresh boot lands on a clean canvas; the toggle brings it back. const [sidebarCollapsed, setSidebarCollapsed] = useState(true); const [renamingDashboardId, setRenamingDashboardId] = useState(null); @@ -433,7 +421,6 @@ const AppShell: React.FC = () => { const isDashboardRoute = location.pathname === '/' || location.pathname.startsWith('/dashboard/'); const isDashboardViewActive = location.pathname.startsWith('/dashboard/'); const isAppsRoute = false; // /apps route removed; app cards live on the dashboard now. - const isCustomizationRoute = location.pathname === '/customization' || CUSTOMIZATION_PATHS.has(location.pathname); const activeDashboardId = location.pathname.startsWith('/dashboard/') ? location.pathname.split('/dashboard/')[1] : null; @@ -819,9 +806,6 @@ const AppShell: React.FC = () => { '& [data-onboarding="sidebar-dashboards"]:hover .MuiListItemIcon-root svg': { transform: 'scale(1.14)', }, - '& [data-onboarding="sidebar-customization"]:hover .MuiListItemIcon-root svg': { - transform: 'rotate(-14deg) scale(1.06)', - }, '& [data-onboarding="sidebar-apps"]:hover .MuiListItemIcon-root svg': { transform: 'rotate(8deg) scale(1.08)', }, @@ -982,106 +966,6 @@ const AppShell: React.FC = () => { {/* Sections separate with air, not lines. */} - - { - if (isCustomizationRoute) { - setCustomizationExpanded((prev) => !prev); - } else { - navigate('/customization'); - setCustomizationExpanded(true); - } - }} - data-onboarding="sidebar-customization" - data-expanded={customizationExpanded ? 'true' : 'false'} - aria-expanded={customizationExpanded} - sx={{ - borderRadius: 1.5, - py: 0.6, - px: 1.25, - bgcolor: isCustomizationRoute ? `${c.accent.primary}12` : 'transparent', - '&:hover': { bgcolor: isCustomizationRoute ? `${c.accent.primary}18` : `${c.text.tertiary}0A` }, - transition: 'background-color 0.15s', - }} - > - - - - - - - - - - {CUSTOMIZATION_ITEMS.map((item) => { - // Manual click handler instead of NavLink: NavLink's internal navigate bypasses our startTransition wrapper. - const isActive = location.pathname === item.path; - return ( - navigate(item.path)} - onMouseEnter={() => { - // Hover-prefetch lazy chunk so click is ~0ms (see Main.tsx for path -> import map). - const fn = (window as any).__openswarmPrefetchRoute; - if (typeof fn === 'function') fn(item.path); - }} - sx={{ - display: 'flex', - alignItems: 'center', - gap: 0.75, - pl: 1.25, - pr: 1, - py: 0.5, - mx: 0.5, - cursor: 'pointer', - // 25% accent alpha needed for readable contrast on dark-mode bg.secondary; 10% muddied to grey. - borderRadius: `${c.radius.md}px`, - bgcolor: isActive ? `${c.accent.primary}40` : 'transparent', - '&:hover': { bgcolor: isActive ? `${c.accent.primary}55` : `${c.text.tertiary}0A` }, - transition: 'background-color 0.12s', - }} - > - - {item.label} - - - ); - })} - - - - - {/* Sections separate with air, not lines. */} - - { const lastShowMeClickRef = useRef(0); const unlockedIds = useUnlockedStepIds(); + const liveStepIds = useMemo(() => new Set(STEPS.map((s) => s.id)), []); const currentStep = useMemo(() => { // Spotlight only lands on an unlocked, not-yet-done step, so we never tell the user to "Show me" something they haven't unlocked yet. const explicit = progress.currentStepId @@ -87,8 +88,11 @@ const OnboardingPanel: React.FC = () => { const stageOf = currentStep?.stage ?? 'get_started'; // Count only what's UNLOCKED, not all 8. A brand-new user sees "0/2" (launch + connect), and the denominator grows as the first win unlocks the rest, so we never dump the whole feature surface on someone before their first output. Guard: never let completed exceed the shown total (data-weirdness safety). - const done = progress.completedSteps.length; - const total = Math.max(unlockedIds.size, done); + const done = progress.completedSteps.filter((id) => liveStepIds.has(id)).length; + const total = Math.max( + Array.from(unlockedIds).filter((id) => liveStepIds.has(id)).length, + done, + ); // Timer lives inside CelebrationView so parent re-renders can't cancel it. const justDoneStepId = progress.justCompletedStepId; diff --git a/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx b/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx index ad4022fa..1c52a8d4 100644 --- a/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx +++ b/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx @@ -32,7 +32,8 @@ const OnboardingRoadmapModal: React.FC = () => { return STEPS.find((s) => !progress.completedSteps.includes(s.id) && unlockedIds.has(s.id)); })(); - const totalDone = progress.completedSteps.length; + // Filter to live steps so a user who finished a since-removed step can't read e.g. 8/6. + const totalDone = progress.completedSteps.filter((id) => findStepById(id)).length; const total = STEPS.length; const jumpToCurrent = () => { diff --git a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx index 3372749d..5c211e3f 100644 --- a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx +++ b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx @@ -13,6 +13,7 @@ import { persistToStorage, markStepCompleted, setPanelMode, + setCurrentStep, markRevealedAfterWin, } from '@/shared/state/onboardingProgressSlice'; import AgenticCursor, { type AgenticCursorHandle } from './ac/AgenticCursor'; @@ -98,6 +99,13 @@ const OnboardingRoot: React.FC = () => { ); }, [progress.initialized, settingsLoaded, dispatch, store]); + useEffect(() => { + if (!progress.initialized || !progress.currentStepId) return; + if (STEPS.some((step) => step.id === progress.currentStepId)) return; + const nextStep = STEPS.find((step) => !(progress.completedSteps ?? []).includes(step.id)); + dispatch(setCurrentStep(nextStep?.id ?? null)); + }, [progress.initialized, progress.currentStepId, progress.completedSteps, dispatch]); + // Bridge Redux signals to bus + auto-mark on skipIf. Coalesces microtask-bursts of dispatches. useEffect(() => { let last = new Set(progress.completedSteps); diff --git a/frontend/src/app/components/Onboarding/ac/acRuntime.ts b/frontend/src/app/components/Onboarding/ac/acRuntime.ts index 7183627f..fc536586 100644 --- a/frontend/src/app/components/Onboarding/ac/acRuntime.ts +++ b/frontend/src/app/components/Onboarding/ac/acRuntime.ts @@ -311,15 +311,11 @@ async function runOp(op: ACOp, ctx: RunContext): Promise { switch (op.kind) { case 'move_to': { - // Order matters: open the whole sidebar first (sub-section markers must exist in DOM), THEN expand Customization, THEN target. + // Open the whole sidebar first so its markers exist in the DOM before we target one. const expandSidebarOps = maybeBuildExpandSidebarOps(op.target); if (expandSidebarOps) { await runOps(expandSidebarOps, ctx); } - const expandOps = maybeBuildExpandCustomizationOps(op.target); - if (expandOps) { - await runOps(expandOps, ctx); - } const el = await waitForSelector(op.target); const scrolled = scrollIntoViewIfNeeded(el); const offX = op.offset?.x ?? 0; @@ -755,25 +751,15 @@ function buildOpenDashboardOps(): ACOp[] { return ops; } -const CUSTOMIZATION_AREA_TARGETS = new Set([ - 'sidebar-actions', - 'sidebar-skills', - 'sidebar-modes', -]); - // `sidebar-toggle` excluded: it lives in the top bar (we click it to expand). Recursing would loop. const SIDEBAR_AREA_TARGETS = new Set([ 'sidebar-settings-button', 'sidebar-dashboards', - 'sidebar-customization', - 'sidebar-skills', - 'sidebar-actions', - 'sidebar-modes', 'sidebar-apps', 'dashboard-row-first', ]); -/** MUST run before maybeBuildExpandCustomizationOps: Customization header is inside the collapsible panel, so expand-check on hidden panel queues an impossible click. */ +/** Expands the collapsed sidebar so its row markers exist before a move_to targets one. */ function maybeBuildExpandSidebarOps(target: string): ACOp[] | null { if (!SIDEBAR_AREA_TARGETS.has(target)) return null; const toggle = document.querySelector( @@ -789,26 +775,6 @@ function maybeBuildExpandSidebarOps(target: string): ACOp[] | null { ]; } -function maybeBuildExpandCustomizationOps(target: string): ACOp[] | null { - if (!CUSTOMIZATION_AREA_TARGETS.has(target)) return null; - const header = document.querySelector( - '[data-onboarding="sidebar-customization"]', - ); - const expanded = - header?.dataset.expanded === 'true' || - header?.getAttribute('aria-expanded') === 'true'; - if (expanded) return null; - return [ - { kind: 'move_to', target: 'sidebar-customization' }, - { kind: 'popup', text: 'Open Customization.' }, - { - kind: 'wait_user', - condition: { kind: 'click_target', target: 'sidebar-customization' }, - timeoutMs: 60000, - }, - ]; -} - interface WaitResult { timedOut: boolean; } diff --git a/frontend/src/app/components/Onboarding/selectors.ts b/frontend/src/app/components/Onboarding/selectors.ts index 1d9c9935..f74a2240 100644 --- a/frontend/src/app/components/Onboarding/selectors.ts +++ b/frontend/src/app/components/Onboarding/selectors.ts @@ -1,9 +1,6 @@ // Central registry of data-onboarding / data-select-type selectors. Step files import S.*; never inline. export const S = { - sidebarSkills: 'sidebar-skills', - sidebarActions: 'sidebar-actions', - sidebarModes: 'sidebar-modes', sidebarApps: 'sidebar-apps', sidebarSettingsButton: 'sidebar-settings-button', @@ -35,22 +32,9 @@ export const S = { chatSendButton: 'chat-send-button', elementSelectionToggle: 'element-selection-toggle', - actionsRedditToggle: 'actions-reddit-toggle', - actionsRedditChevron: 'actions-reddit-chevron', - actionsSubredditsChevron: 'actions-subreddits-chevron', - actionsPermissionToggle: 'actions-permission-toggle', - actionsYoutubeToggle: 'actions-youtube-toggle', - actionsYoutubeChevron: 'actions-youtube-chevron', - canvasFitToView: 'canvas-fit-to-view', canvasTidyLayout: 'canvas-tidy-layout', canvasMinimapToggle: 'canvas-minimap-toggle', - /** Header for sidebar's Customization section; runtime auto-expands before targeting children. */ - sidebarCustomization: 'sidebar-customization', - - skillItemPdf: 'skill-item-pdf', - skillInstallButton: 'skill-install-button', - skillBuilderFab: 'skill-builder-fab', appsNewButton: 'apps-new-button', appCardLatest: 'app-card-latest', diff --git a/frontend/src/app/components/Onboarding/steps/index.ts b/frontend/src/app/components/Onboarding/steps/index.ts index 5b218938..3b3e0bbf 100644 --- a/frontend/src/app/components/Onboarding/steps/index.ts +++ b/frontend/src/app/components/Onboarding/steps/index.ts @@ -1,11 +1,9 @@ import type { OnboardingStep, StepStage } from './types'; import { step01 } from './step01_connectModel'; -import { step02 } from './step02_enableActions'; import { step03 } from './step03_launchAgent'; import { step04 } from './step04_useBrowser'; import { step05 } from './step05_agentUseBrowser'; import { step06 } from './step06_agentControlAgents'; -import { step07 } from './step07_installSkill'; import { step08 } from './step08_makeApp'; import { welcomeOpenStep } from './step00_welcomeNudge'; @@ -13,11 +11,9 @@ import { welcomeOpenStep } from './step00_welcomeNudge'; export const STEPS: OnboardingStep[] = [ step03, step01, - step02, step04, step05, step06, - step07, step08, ]; diff --git a/frontend/src/app/components/Onboarding/steps/skipPredicates.ts b/frontend/src/app/components/Onboarding/steps/skipPredicates.ts index 73082e7d..781398d1 100644 --- a/frontend/src/app/components/Onboarding/steps/skipPredicates.ts +++ b/frontend/src/app/components/Onboarding/steps/skipPredicates.ts @@ -76,18 +76,6 @@ export function hasAnySkillInstalled(s: RootState): boolean { return Object.keys(items).length > 0; } -/** True if PDF skill installed (id/name/command); step 7 uses this so other skills don't auto-skip. */ -export function hasPdfSkillInstalled(s: RootState): boolean { - const items = s.skills?.items as any; - const list: any[] = Array.isArray(items) ? items : Object.values(items ?? {}); - return list.some((sk: any) => { - const id = (sk?.id ?? '').toString().toLowerCase(); - const name = (sk?.name ?? '').toString().toLowerCase(); - const cmd = (sk?.command ?? '').toString().toLowerCase(); - return id.includes('pdf') || name.includes('pdf') || cmd.includes('pdf'); - }); -} - /** True if a browser card exists; step 4 auto-skips the open-a-browser walkthrough. */ export function hasAnyBrowserSpawned(s: RootState): boolean { const cards = (s as any).dashboardLayout?.browserCards ?? {}; diff --git a/frontend/src/app/components/Onboarding/steps/step02_enableActions.ts b/frontend/src/app/components/Onboarding/steps/step02_enableActions.ts deleted file mode 100644 index dc220549..00000000 --- a/frontend/src/app/components/Onboarding/steps/step02_enableActions.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { OnboardingStep } from './types'; -import { S } from '../selectors'; -import { isYoutubeEnabled } from './skipPredicates'; - -export const step02: OnboardingStep = { - id: 'enable_actions', - // Demoted out of the first-run path: a feature to discover after the first win. - stage: 'learn_features', - index: 3, - title: 'Enable agentic actions', - description: 'Allow agents to work across your apps.', - videoSrc: './onboarding-videos/v2/02.mp4', - videoDurationLabel: '0:24', - // Narrowed to YouTube so users with other tools still get walked. - skipIf: isYoutubeEnabled, - // Two beats only (open Actions, flip YouTube on); the chevron-peek and permission fine-tune popups were trimmed to give the step room to breathe. - ops: [ - { kind: 'move_to', target: S.sidebarActions }, - { kind: 'popup', text: 'Open Actions.' }, - { - kind: 'wait_user', - condition: { kind: 'click_target', target: S.sidebarActions }, - }, - // YouTube on the throughline; step 3 needs it. Waits on Redux state, not click, so toggling stays synced. - { kind: 'move_to', target: S.actionsYoutubeToggle }, - { kind: 'popup', text: 'Flip YouTube on.' }, - { - kind: 'wait_user', - condition: { - kind: 'redux_predicate', - selector: isYoutubeEnabled, - truthy: true, - }, - timeoutMs: 90000, - }, - { kind: 'delay', ms: 1200 }, - { kind: 'outro' }, - ], -}; diff --git a/frontend/src/app/components/Onboarding/steps/step07_installSkill.ts b/frontend/src/app/components/Onboarding/steps/step07_installSkill.ts deleted file mode 100644 index a3cd61a8..00000000 --- a/frontend/src/app/components/Onboarding/steps/step07_installSkill.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { OnboardingStep } from './types'; -import { S } from '../selectors'; -import { hasPdfSkillInstalled } from './skipPredicates'; - -export const step07: OnboardingStep = { - id: 'install_skill', - stage: 'learn_features', - index: 7, - title: 'Install a skill', - description: 'Teach agents how to handle specific tasks.', - videoSrc: './onboarding-videos/v2/07.mp4', - videoDurationLabel: '0:24', - // Narrowed to PDF so other-skill users still walk through this demo. - skipIf: hasPdfSkillInstalled, - ops: [ - { kind: 'move_to', target: S.sidebarSkills }, - { kind: 'popup', text: 'Wander into Skills.' }, - { - kind: 'wait_user', - condition: { kind: 'click_target', target: S.sidebarSkills }, - }, - { kind: 'move_to', target: S.skillItemPdf }, - { kind: 'popup', text: 'Pick the PDF one.' }, - { - kind: 'wait_user', - condition: { kind: 'click_target', target: S.skillItemPdf }, - }, - { kind: 'move_to', target: S.skillInstallButton }, - { kind: 'popup', text: 'Install it!' }, - { - kind: 'wait_user', - condition: { kind: 'event_bus', event: 'skill:installed' }, - timeoutMs: 60000, - }, - { - kind: 'popup', - text: 'Boom! Now any chat is way better with PDFs.', - }, - { kind: 'move_to', target: S.skillBuilderFab }, - { kind: 'click', target: S.skillBuilderFab, simulate: true }, - { - kind: 'popup', - text: 'Got an idea? Type it here and the skill builder whips one up.', - }, - { kind: 'delay', ms: 3500 }, - { kind: 'outro' }, - ], -}; diff --git a/frontend/src/app/components/overlays/GlobalSearchPalette.tsx b/frontend/src/app/components/overlays/GlobalSearchPalette.tsx index 2c86b398..e792e4c9 100644 --- a/frontend/src/app/components/overlays/GlobalSearchPalette.tsx +++ b/frontend/src/app/components/overlays/GlobalSearchPalette.tsx @@ -53,7 +53,6 @@ const ACTIONS: ActionResult[] = [ { kind: 'action', id: 'settings-models', name: 'Connect a model', keywords: 'settings models api key provider subscription' }, { kind: 'action', id: 'go-skills', name: 'Go to Skills', keywords: 'customize skills' }, { kind: 'action', id: 'go-actions', name: 'Go to Actions', keywords: 'customize tools actions mcp' }, - { kind: 'action', id: 'go-modes', name: 'Go to Modes', keywords: 'customize modes' }, { kind: 'action', id: 'all-dashboards', name: 'All dashboards', keywords: 'overview picker browse boards' }, ]; @@ -149,9 +148,9 @@ const GlobalSearchPalette: React.FC = ({ open, onClose }) => { break; case 'settings': dispatch(openSettingsModal()); break; case 'settings-models': dispatch(openSettingsModal('models')); break; - case 'go-skills': navigate('/skills'); break; - case 'go-actions': navigate('/actions'); break; - case 'go-modes': navigate('/modes'); break; + // Skills/Actions live in Settings now (the sidebar Customization section moved there). + case 'go-skills': dispatch(openSettingsModal('skills')); break; + case 'go-actions': dispatch(openSettingsModal('tools')); break; case 'all-dashboards': navigate('/'); break; } }, [dispatch, navigate]); diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index f5378152..ec28d865 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useMemo, useState, useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; @@ -279,7 +279,6 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose ); }); const testState = useAppSelector((s) => (id ? s.agents.sessions[id]?.workflow_test_state : null) ?? null); - const navigate = useNavigate(); const dispatch = useAppDispatch(); const session = useAppSelector((state) => (id ? state.agents.sessions[id] : undefined)); const modesMap = useAppSelector((state) => state.modes.items); @@ -1721,7 +1720,7 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose setActivateError(`Activation failed (${r.status})`); } else if (body?.status === 'unknown_server') { // Not yet connected; jump to Actions so the user can finish OAuth. - navigate('/actions'); + dispatch(openSettingsModal('tools')); } else if (id) { dispatch(clearMcpSuggestions({ sessionId: id })); } @@ -2198,7 +2197,7 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose setActivateError(`Activation failed (${r.status})`); } else if (body?.status === 'unknown_server') { // Not yet connected; jump straight to Actions so the user can finish OAuth. Nothing here can do it on their behalf. - navigate('/actions'); + dispatch(openSettingsModal('tools')); } else if (id) { // Activation succeeded; clear the banner so the user gets visual confirmation the click did something. dispatch(clearMcpSuggestions({ sessionId: id })); diff --git a/frontend/src/app/pages/Customization/Customization.tsx b/frontend/src/app/pages/Customization/Customization.tsx deleted file mode 100644 index 3075da07..00000000 --- a/frontend/src/app/pages/Customization/Customization.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import Card from '@mui/material/Card'; -import CardActionArea from '@mui/material/CardActionArea'; -import PsychologyIcon from '@mui/icons-material/Psychology'; -import BuildIcon from '@mui/icons-material/Build'; -import TuneIcon from '@mui/icons-material/Tune'; -import { useClaudeTokens } from '@/shared/styles/ThemeContext'; - -const PANELS = [ - { - label: 'Skills', - path: '/skills', - icon: , - description: - 'Install or author reusable skill packages that teach your agents new capabilities and workflows.', - }, - { - label: 'Actions', - path: '/actions', - icon: , - description: - 'Define and manage the actions your agents can take.', - }, - { - label: 'Modes', - path: '/modes', - icon: , - description: - 'Configure agent interaction modes with custom system prompts, allowed actions, and auto-switching rules.', - }, -]; - -const Customization: React.FC = () => { - const c = useClaudeTokens(); - const navigate = useNavigate(); - - return ( - - - - - Customization - - - Tailor how your agents behave, what they can do, and how they interact. - - - - - {PANELS.map((panel) => ( - - navigate(panel.path)} - sx={{ p: 3, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1.5 }} - > - - {React.cloneElement(panel.icon, { sx: { fontSize: 24 } })} - - - {panel.label} - - - {panel.description} - - - - ))} - - - - ); -}; - -export default Customization; diff --git a/frontend/src/app/pages/Modes/Modes.tsx b/frontend/src/app/pages/Modes/Modes.tsx deleted file mode 100644 index 55fadb9c..00000000 --- a/frontend/src/app/pages/Modes/Modes.tsx +++ /dev/null @@ -1,604 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import CardActions from '@mui/material/CardActions'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import DialogActions from '@mui/material/DialogActions'; -import TextField from '@mui/material/TextField'; -import IconButton from '@mui/material/IconButton'; -import Chip from '@mui/material/Chip'; -import CircularProgress from '@mui/material/CircularProgress'; -import Tooltip from '@mui/material/Tooltip'; -import { Skeleton } from '@/app/components/feedback/Loading'; -import FormControl from '@mui/material/FormControl'; -import InputLabel from '@mui/material/InputLabel'; -import Select from '@mui/material/Select'; -import MenuItem from '@mui/material/MenuItem'; -import Checkbox from '@mui/material/Checkbox'; -import ListItemText from '@mui/material/ListItemText'; -import OutlinedInput from '@mui/material/OutlinedInput'; -import AddIcon from '@mui/icons-material/Add'; -import EditIcon from '@mui/icons-material/Edit'; -import DeleteIcon from '@mui/icons-material/Delete'; -import TuneIcon from '@mui/icons-material/Tune'; -import LockIcon from '@mui/icons-material/Lock'; -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import RestoreIcon from '@mui/icons-material/Restore'; -import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; -import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined'; -import MapOutlinedIcon from '@mui/icons-material/MapOutlined'; -import CategoryOutlinedIcon from '@mui/icons-material/CategoryOutlined'; -import { useAppDispatch, useAppSelector } from '@/shared/hooks'; -import { - fetchModes, - createMode, - updateMode, - deleteMode, - resetMode, - Mode, -} from '@/shared/state/modesSlice'; -import { fetchBuiltinTools, fetchTools } from '@/shared/state/toolsSlice'; -import { fetchSkills } from '@/shared/state/skillsSlice'; -import FolderOpenIcon from '@mui/icons-material/FolderOpen'; -import ExtensionIcon from '@mui/icons-material/Extension'; -import ListSubheader from '@mui/material/ListSubheader'; -import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import DirectoryBrowser from '@/app/components/editor/DirectoryBrowser'; -import RichPromptEditor from '@/app/components/editor/RichPromptEditor'; - -const ICON_MAP: Record = { - smart_toy: , - question_answer: , - map: , - category: , - tune: , -}; - -const ICON_OPTIONS = [ - { value: 'smart_toy', label: 'Robot' }, - { value: 'question_answer', label: 'Q&A' }, - { value: 'map', label: 'Map' }, - { value: 'category', label: 'Category' }, - { value: 'tune', label: 'Tune' }, -]; - -const COLOR_OPTIONS = [ - { value: '#ae5630', label: 'Terra Cotta' }, - { value: '#4ade80', label: 'Green' }, - { value: '#fbbf24', label: 'Amber' }, - { value: '#f87171', label: 'Red' }, - { value: '#38bdf8', label: 'Sky' }, - { value: '#c084fc', label: 'Purple' }, - { value: '#fb923c', label: 'Orange' }, - { value: '#2dd4bf', label: 'Teal' }, -]; - -interface ModeForm { - name: string; - description: string; - system_prompt: string; - tools: string[]; - toolsEnabled: boolean; - default_next_mode: string; - icon: string; - color: string; - default_folder: string; -} - -const emptyForm: ModeForm = { - name: '', - description: '', - system_prompt: '', - tools: [], - toolsEnabled: false, - default_next_mode: '', - icon: 'smart_toy', - color: '#ae5630', - default_folder: '', -}; - -const ALL_BUILTIN_TOOL_NAMES = ['Read', 'Edit', 'Write', 'Bash', 'Glob', 'Grep', 'AskUserQuestion']; - -const Modes: React.FC = () => { - const c = useClaudeTokens(); - const dispatch = useAppDispatch(); - const { items, builtinDefaults, loading } = useAppSelector((s) => s.modes); - const toolItems = useAppSelector((s) => s.tools.items); - const modes = useMemo(() => Object.values(items), [items]); - - const mcpToolNames = useMemo(() => { - return Object.values(toolItems) - .filter((t) => t.mcp_config && Object.keys(t.mcp_config).length > 0 && t.auth_status !== 'none') - .map((t) => `mcp:${t.name}`); - }, [toolItems]); - - const [dialogOpen, setDialogOpen] = useState(false); - const [editingId, setEditingId] = useState(null); - const [form, setForm] = useState(emptyForm); - const [browseOpen, setBrowseOpen] = useState(false); - - useEffect(() => { - dispatch(fetchModes()); - dispatch(fetchBuiltinTools()); - dispatch(fetchTools()); - dispatch(fetchSkills()); - }, [dispatch]); - - const openCreate = () => { - setEditingId(null); - setForm(emptyForm); - setDialogOpen(true); - }; - - const openEdit = (mode: Mode) => { - setEditingId(mode.id); - setForm({ - name: mode.name, - description: mode.description, - system_prompt: mode.system_prompt ?? '', - tools: mode.tools ?? [], - toolsEnabled: mode.tools !== null, - default_next_mode: mode.default_next_mode ?? '', - icon: mode.icon, - color: mode.color, - default_folder: mode.default_folder ?? '', - }); - setDialogOpen(true); - }; - - const handleSave = async () => { - const payload = { - name: form.name, - description: form.description, - system_prompt: form.system_prompt || null, - tools: form.toolsEnabled ? form.tools : null, - default_next_mode: form.default_next_mode || null, - icon: form.icon, - color: form.color, - default_folder: form.default_folder || null, - }; - - if (editingId) { - await dispatch(updateMode({ id: editingId, ...payload })); - } else { - await dispatch(createMode(payload as any)); - } - setDialogOpen(false); - }; - - const handleDelete = async (id: string) => { - await dispatch(deleteMode(id)); - }; - - const editingIsBuiltin = editingId ? items[editingId]?.is_builtin ?? false : false; - - const hasDiverged = useMemo(() => { - if (!editingId || !editingIsBuiltin) return false; - const defaults = builtinDefaults[editingId]; - if (!defaults) return false; - const current = items[editingId]; - if (!current) return false; - return ( - current.name !== defaults.name || - current.description !== defaults.description || - (current.system_prompt ?? '') !== (defaults.system_prompt ?? '') || - JSON.stringify(current.tools) !== JSON.stringify(defaults.tools) || - (current.default_next_mode ?? '') !== (defaults.default_next_mode ?? '') || - current.icon !== defaults.icon || - current.color !== defaults.color || - (current.default_folder ?? '') !== (defaults.default_folder ?? '') - ); - }, [editingId, editingIsBuiltin, items, builtinDefaults]); - - const handleReset = async () => { - if (!editingId) return; - const action = await dispatch(resetMode(editingId)); - if (resetMode.fulfilled.match(action)) { - const m = action.payload; - setForm({ - name: m.name, - description: m.description, - system_prompt: m.system_prompt ?? '', - tools: m.tools ?? [], - toolsEnabled: m.tools !== null, - default_next_mode: m.default_next_mode ?? '', - icon: m.icon, - color: m.color, - default_folder: m.default_folder ?? '', - }); - } - }; - - const otherModes = modes.filter((m) => m.id !== editingId); - - return ( - - - - - Modes - - - Configure agent interaction modes with custom system prompts, actions, and auto-switching. - - - - - - {loading ? ( - - {[0, 1, 2, 3, 4, 5].map((i) => ( - - ))} - - ) : modes.length === 0 ? ( - - - No modes defined yet. Create one to get started. - - ) : ( - - {modes.map((mode) => ( - - - - - {ICON_MAP[mode.icon] || ICON_MAP.smart_toy} - - - {mode.name} - - {mode.is_builtin && ( - } - label="Built-in" - size="small" - sx={{ bgcolor: c.bg.secondary, color: c.text.muted, fontSize: '0.7rem', height: 22 }} - /> - )} - - {mode.description && ( - - {mode.description} - - )} - - {mode.tools !== null ? ( - - ) : ( - - )} - {mode.system_prompt && ( - - )} - {mode.default_next_mode && ( - } - label={items[mode.default_next_mode]?.name || mode.default_next_mode} - size="small" - sx={{ bgcolor: 'rgba(251,191,36,0.15)', color: '#fbbf24', fontSize: '0.75rem', height: 24 }} - /> - )} - {mode.default_folder && ( - } - label={mode.default_folder.split('/').pop() || mode.default_folder} - size="small" - sx={{ bgcolor: 'rgba(56,189,248,0.15)', color: '#38bdf8', fontSize: '0.75rem', height: 24 }} - /> - )} - - - - - openEdit(mode)} sx={{ color: c.text.tertiary, '&:hover': { color: c.accent.primary } }}> - - - - {!mode.is_builtin && ( - - handleDelete(mode.id)} sx={{ color: c.text.tertiary, '&:hover': { color: c.status.error } }}> - - - - )} - - - ))} - - )} - - setDialogOpen(false)} - maxWidth="md" - fullWidth - PaperProps={{ - sx: { bgcolor: c.bg.surface, backgroundImage: 'none', borderRadius: 4, border: `1px solid ${c.border.subtle}` }, - }} - > - - {editingId ? 'Edit Mode' : 'New Mode'} - - - setForm({ ...form, name: e.target.value })} - fullWidth - size="small" - sx={{ '& .MuiOutlinedInput-root': { bgcolor: c.bg.page } }} - /> - setForm({ ...form, description: e.target.value })} - fullWidth - size="small" - sx={{ '& .MuiOutlinedInput-root': { bgcolor: c.bg.page } }} - /> - setForm({ ...form, system_prompt: v })} - placeholder="Instructions for the agent when using this mode... (@ for context, / for commands)" - minRows={3} - maxRows={8} - /> - - - - setForm({ ...form, toolsEnabled: e.target.checked, tools: e.target.checked ? form.tools : [] })} - size="small" - sx={{ color: c.text.tertiary, '&.Mui-checked': { color: c.accent.primary }, p: 0 }} - /> - - Restrict actions {!form.toolsEnabled && (all actions allowed)} - - - {form.toolsEnabled && ( - - Allowed Actions - - - )} - - - - Default Next Mode - - - - - - Default Folder - - - setForm({ ...form, default_folder: e.target.value })} - fullWidth - size="small" - placeholder="Not set (uses global default)" - sx={{ - '& .MuiOutlinedInput-root': { - bgcolor: c.bg.page, - fontFamily: 'monospace', - fontSize: '0.85rem', - }, - }} - /> - - - - - - - Icon - - - - Color - - - - - - - {editingIsBuiltin && ( - - - - - - )} - - - - - - - - - setBrowseOpen(false)} - onSelect={(item) => setForm({ ...form, default_folder: item.path })} - initialPath={form.default_folder || ''} - /> - - ); -}; - -export default Modes; diff --git a/frontend/src/app/pages/Settings/Settings.tsx b/frontend/src/app/pages/Settings/Settings.tsx index e1612023..f1eacc28 100644 --- a/frontend/src/app/pages/Settings/Settings.tsx +++ b/frontend/src/app/pages/Settings/Settings.tsx @@ -4,6 +4,7 @@ import Snackbar from '@mui/material/Snackbar'; import Alert from '@mui/material/Alert'; import Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; +import CircularProgress from '@mui/material/CircularProgress'; import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { updateSettingsPatch, closeSettingsModal, AppSettings } from '@/shared/state/settingsSlice'; import { onboardingBus } from '@/app/components/Onboarding/eventBus'; @@ -18,6 +19,10 @@ import UsageStats from './sections/usage/UsageStats'; import SettingsHeader from './sections/SettingsHeader'; import { makeSettingsStyles } from './sections/settingsStyles'; +// Skills/Tools moved here from the old sidebar Customization section; lazy since both pull heavy deps and Settings opens nearly every session. +const SkillsTab = React.lazy(() => import('@/app/pages/Skills/Skills')); +const ToolsTab = React.lazy(() => import('@/app/pages/Tools/Tools')); + // Brand colors for provider group headers; mirrors ChatInput picker. const PROVIDER_COLORS: Record = { anthropic: '#E8927A', @@ -85,7 +90,7 @@ const Settings: React.FC = () => { }, [modelsByProvider, modelsLoaded, settings.connection_mode, settings.default_model]); const initialTab = useAppSelector((s) => s.settings.initialTab); - const TAB_VALUES = ['general', 'models', 'usage', 'commands'] as const; + const TAB_VALUES = ['general', 'models', 'skills', 'tools', 'commands', 'usage'] as const; type SettingsTab = typeof TAB_VALUES[number]; const isValidTab = (t: string | null | undefined): t is SettingsTab => !!t && (TAB_VALUES as readonly string[]).includes(t); @@ -219,6 +224,8 @@ const Settings: React.FC = () => { sx: { width: 780, height: '85vh', + display: 'flex', + flexDirection: 'column', bgcolor: c.bg.page, borderRadius: 2, border: `1px solid ${c.border.subtle}`, @@ -236,6 +243,8 @@ const Settings: React.FC = () => { { + ) : activeTab === 'skills' ? ( + + }> + + + + ) : activeTab === 'tools' ? ( + + }> + + + ) : ( diff --git a/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx b/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx index c81fa4c2..a012c273 100644 --- a/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx +++ b/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx @@ -54,8 +54,10 @@ const SettingsHeader: React.FC<{ > - + + + ); diff --git a/frontend/src/app/pages/Skills/Skills.tsx b/frontend/src/app/pages/Skills/Skills.tsx index 80c2104f..8399a2a9 100644 --- a/frontend/src/app/pages/Skills/Skills.tsx +++ b/frontend/src/app/pages/Skills/Skills.tsx @@ -128,7 +128,7 @@ const Skills: React.FC = () => { const regGrouped = useMemo(() => { const groups: Record = {}; - const q = searchFilter.toLowerCase(); + const q = searchFilter.trim().toLowerCase(); for (const sk of regSkills) { if (q && !sk.name.toLowerCase().includes(q) && !sk.description.toLowerCase().includes(q)) continue; const cat = sk.category || 'General'; @@ -139,7 +139,7 @@ const Skills: React.FC = () => { }, [regSkills, searchFilter]); const filteredLocal = useMemo(() => { - const q = searchFilter.toLowerCase(); + const q = searchFilter.trim().toLowerCase(); if (!q) return localSkills; return localSkills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q)); }, [localSkills, searchFilter]); diff --git a/frontend/src/app/pages/Tools/Tools.tsx b/frontend/src/app/pages/Tools/Tools.tsx index c1048263..02f4c54a 100644 --- a/frontend/src/app/pages/Tools/Tools.tsx +++ b/frontend/src/app/pages/Tools/Tools.tsx @@ -106,8 +106,8 @@ const Tools: React.FC = () => { - Action Library - Define and manage custom actions for your Claude Code agents. + Tool Library + Define and manage custom tools for your Claude Code agents. { > {builtinSectionOpen ? : } - Built-in Action Sets + Built-in {coreTools.length > 0 && ( - } count={coreTools.length} open={coreSectionOpen} onToggle={() => setCoreSectionOpen((v) => !v)} grouped={groupedCore} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={coreSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(coreTools, v)} /> + } count={coreTools.length} open={coreSectionOpen} onToggle={() => setCoreSectionOpen((v) => !v)} grouped={groupedCore} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={coreSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(coreTools, v)} /> )} {deferredTools.length > 0 && ( - } count={deferredTools.length} open={deferredSectionOpen} onToggle={() => setDeferredSectionOpen((v) => !v)} grouped={groupedDeferred} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} deferred builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={deferredSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(deferredTools, v)} /> + } count={deferredTools.length} open={deferredSectionOpen} onToggle={() => setDeferredSectionOpen((v) => !v)} grouped={groupedDeferred} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} deferred builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={deferredSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(deferredTools, v)} /> )} {browserTools.length > 0 && ( @@ -183,7 +183,7 @@ const Tools: React.FC = () => { setCustomSectionOpen((v) => !v)} sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 1, cursor: 'pointer', userSelect: 'none', '&:hover .section-arrow': { color: c.text.secondary } }}> {customSectionOpen ? : } - Custom Action Sets + Connections @@ -196,7 +196,7 @@ const Tools: React.FC = () => { ) : (tools.length === 0 && uninstalledIntegrations.length === 0) ? ( - No custom actions defined yet. Create one to get started. + No custom tools defined yet. Create one to get started. ) : ( diff --git a/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx b/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx index 7dad43c1..5fb2ce38 100644 --- a/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx +++ b/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx @@ -64,9 +64,9 @@ const BrowserPermissionCard: React.FC = ({ Browser - + - Browser automation delegation and individual browser actions + Browser automation delegation and individual browser tools e.stopPropagation()}> = ({ - Action Permissions - + Tool Permissions + @@ -156,7 +156,7 @@ const BrowserPermissionCard: React.FC = ({ > - Browser Actions + Browser Tools e.stopPropagation()}> diff --git a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx index 05248bd1..d6c7da39 100644 --- a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx +++ b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx @@ -129,7 +129,7 @@ const CustomToolCard: React.FC = ({ } label="Configured" size="small" sx={{ bgcolor: c.status.warningBg, color: c.status.warning, fontSize: '0.7rem', height: 20, '& .MuiChip-icon': { color: c.status.warning } }} /> )} {ig && totalToolCount > 0 && ( - + )} {ig && ( } label="docs" size="small" sx={{ bgcolor: c.bg.secondary, color: c.text.ghost, fontSize: '0.65rem', height: 18, '& .MuiChip-label': { px: 0.4 }, '& .MuiChip-icon': { ml: 0.4, fontSize: 10 } }} /> @@ -190,13 +190,13 @@ const CustomToolCard: React.FC = ({ - Action Permissions - {hasPerms && } + Tool Permissions + {hasPerms && } {hasPerms && ( <> - + @@ -208,7 +208,7 @@ const CustomToolCard: React.FC = ({ )} - + handleDiscover(tool.id)} @@ -224,7 +224,7 @@ const CustomToolCard: React.FC = ({ {!hasPerms ? ( - No actions discovered yet + No tools discovered yet {!canDiscover && ( - Add an MCP configuration to enable action discovery + Add an MCP configuration to enable tool discovery )} ) : ( diff --git a/frontend/src/app/pages/Tools/cards/ToolSection.tsx b/frontend/src/app/pages/Tools/cards/ToolSection.tsx index 9c32964e..68597e49 100644 --- a/frontend/src/app/pages/Tools/cards/ToolSection.tsx +++ b/frontend/src/app/pages/Tools/cards/ToolSection.tsx @@ -84,8 +84,8 @@ const ToolSection: React.FC = ({ const overallPolicy = getCatGroupPolicy(allSectionTools); const categoryCount = CATEGORY_ORDER.filter((cat) => grouped[cat]).length; const sectionDescription = deferred - ? 'On-demand actions loaded via ToolSearch for planning, scheduling, and extended operations' - : 'Built-in Claude Agent SDK actions for file operations, shell commands, and search'; + ? 'On-demand tools loaded via ToolSearch for planning, scheduling, and extended operations' + : 'Built-in Claude Agent SDK tools for file operations, shell commands, and search'; const firstSentence = (desc: string) => { if (!desc) return ''; @@ -110,7 +110,7 @@ const ToolSection: React.FC = ({ {label} - + {deferred && ( )} @@ -139,8 +139,8 @@ const ToolSection: React.FC = ({ - Action Permissions - + Tool Permissions + diff --git a/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts b/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts index a311b0b3..520f9a5a 100644 --- a/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts +++ b/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts @@ -140,10 +140,10 @@ export function useRegistryBrowser({ regServersRaw, setSnackbar, setEditingId, s })); if (createTool.fulfilled.match(result)) { const newTool = result.payload; - setSnackbar({ open: true, message: `Installed "${f.name}", discovering actions…` }); + setSnackbar({ open: true, message: `Installed "${f.name}", discovering tools…` }); const discoverResult = await dispatch(discoverTools(newTool.id)); if (discoverTools.fulfilled.match(discoverResult)) { - setSnackbar({ open: true, message: `${f.name} ready, actions discovered` }); + setSnackbar({ open: true, message: `${f.name} ready, tools discovered` }); } else { const detail = (discoverResult as any).error?.message || 'discovery failed; the MCP server may need setup first'; diff --git a/frontend/src/app/pages/Tools/hooks/useToolConnections.ts b/frontend/src/app/pages/Tools/hooks/useToolConnections.ts index 68ca30ea..158b6631 100644 --- a/frontend/src/app/pages/Tools/hooks/useToolConnections.ts +++ b/frontend/src/app/pages/Tools/hooks/useToolConnections.ts @@ -45,7 +45,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De const afterConnect = async () => { const statusResult = await dispatch(fetchToolStatus(toolId)); if (fetchToolStatus.fulfilled.match(statusResult) && statusResult.payload.auth_status === 'connected') { - setSnackbar({ open: true, message: 'Account connected! Discovering actions…' }); + setSnackbar({ open: true, message: 'Account connected! Discovering tools…' }); setExpandedToolId(toolId); dispatch(discoverTools(toolId)); } else { @@ -97,7 +97,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De if (status === 'connected') { clearInterval(poll); setDeviceCodeStatus('connected'); - setSnackbar({ open: true, message: `Connected to Microsoft 365${email ? ` as ${email}` : ''}! Discovering actions…` }); + setSnackbar({ open: true, message: `Connected to Microsoft 365${email ? ` as ${email}` : ''}! Discovering tools…` }); setDeviceCodeDialogOpen(false); setExpandedToolId(toolId); await dispatch(fetchToolStatus(toolId)); @@ -148,7 +148,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De })); if (updateTool.fulfilled.match(result)) { setCredDialogOpen(false); - setSnackbar({ open: true, message: `${credDialogIntegration.name} connected! Re-discovering actions…` }); + setSnackbar({ open: true, message: `${credDialogIntegration.name} connected! Re-discovering tools…` }); dispatch(discoverTools(credDialogToolId)); } else { setSnackbar({ open: true, message: 'Failed to save credentials', severity: 'error' }); @@ -177,7 +177,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De })); if (updateTool.fulfilled.match(result)) { setCredDialogOpen(false); - setSnackbar({ open: true, message: 'Slack connected! Re-discovering actions…' }); + setSnackbar({ open: true, message: 'Slack connected! Re-discovering tools…' }); dispatch(discoverTools(credDialogToolId)); } else { setSnackbar({ open: true, message: 'Failed to save Slack credentials', severity: 'error' }); diff --git a/frontend/src/app/pages/Tools/hooks/useToolsActions.ts b/frontend/src/app/pages/Tools/hooks/useToolsActions.ts index 5b0d1ba6..9a3c6916 100644 --- a/frontend/src/app/pages/Tools/hooks/useToolsActions.ts +++ b/frontend/src/app/pages/Tools/hooks/useToolsActions.ts @@ -56,12 +56,12 @@ export function useToolsActions({ items, allTools, regServersRaw, closeMenu }: T } else if (existing && existing.enabled === false) { await dispatch(updateTool({ id: existing.id, enabled: true })); if (integration.authType === 'oauth2' && existing.auth_status !== 'connected') { - setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover actions` }); + setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover tools` }); } else { - setSnackbar({ open: true, message: `Enabled ${integration.name}, re-discovering actions…` }); + setSnackbar({ open: true, message: `Enabled ${integration.name}, re-discovering tools…` }); const discoverResult = await dispatch(discoverTools(existing.id)); if (discoverTools.fulfilled.match(discoverResult)) { - setSnackbar({ open: true, message: `${integration.name} ready, actions discovered` }); + setSnackbar({ open: true, message: `${integration.name} ready, tools discovered` }); } else { const detail = (discoverResult as any).error?.message || 'discovery failed'; setSnackbar({ open: true, message: `${integration.name}: ${detail}`, severity: 'error' }); @@ -80,12 +80,12 @@ export function useToolsActions({ items, allTools, regServersRaw, closeMenu }: T if (createTool.fulfilled.match(result)) { const newTool = result.payload; if (integration.authType === 'oauth2' || integration.authType === 'device_code') { - setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover actions` }); + setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover tools` }); } else { - setSnackbar({ open: true, message: `Enabled ${integration.name}, discovering actions…` }); + setSnackbar({ open: true, message: `Enabled ${integration.name}, discovering tools…` }); const discoverResult = await dispatch(discoverTools(newTool.id)); if (discoverTools.fulfilled.match(discoverResult)) { - setSnackbar({ open: true, message: `${integration.name} ready, actions discovered` }); + setSnackbar({ open: true, message: `${integration.name} ready, tools discovered` }); } else { const detail = (discoverResult as any).error?.message || `discovery failed; is ${integration.mcp_config.command || 'the server'} installed?`; @@ -104,7 +104,7 @@ export function useToolsActions({ items, allTools, regServersRaw, closeMenu }: T try { const result = await dispatch(discoverTools(toolId)); if (discoverTools.fulfilled.match(result)) { - setSnackbar({ open: true, message: 'Actions discovered successfully' }); + setSnackbar({ open: true, message: 'Tools discovered successfully' }); } else { const detail = (result as any).error?.message || 'Discovery failed; is the MCP server running?'; setSnackbar({ open: true, message: detail, severity: 'error' });