From 7444082b65ebb2a0259a8e1533322f90c70ae265 Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Fri, 10 Apr 2026 21:44:04 +0300 Subject: [PATCH] feat(ui): add toasts for create edit and run flows - add global ToastProvider and useToast hook mounted at app root - show success/error toasts for environment, credential, snippet, and scenario create/edit actions - show run-started/error toasts on scenario run actions with 100ms exit animation --- client/src/main.tsx | 9 +- .../pages/credential/CreateCredentialPage.tsx | 8 +- .../pages/credential/EditCredentialPage.tsx | 8 +- .../environment/CreateEnvironmentPage.tsx | 8 +- .../pages/environment/EditEnvironmentPage.tsx | 8 +- client/src/pages/run/RunsPage.tsx | 11 +- .../src/pages/scenario/CreateScenarioPage.tsx | 8 +- client/src/pages/scenario/CreateStepPage.tsx | 8 +- .../src/pages/scenario/EditScenarioPage.tsx | 8 +- client/src/pages/scenario/EditStepPage.tsx | 8 +- .../src/pages/scenario/ScenarioDetailPage.tsx | 11 +- client/src/pages/scenario/ScenariosPage.tsx | 12 ++- .../src/pages/snippet/CreateSnippetPage.tsx | 8 +- client/src/pages/snippet/EditSnippetPage.tsx | 8 +- client/src/ui/Toast/ToastProvider.module.css | 87 +++++++++++++++ client/src/ui/Toast/ToastProvider.tsx | 101 ++++++++++++++++++ client/src/ui/index.ts | 3 + client/tsconfig.tsbuildinfo | 2 +- 18 files changed, 285 insertions(+), 31 deletions(-) create mode 100644 client/src/ui/Toast/ToastProvider.module.css create mode 100644 client/src/ui/Toast/ToastProvider.tsx diff --git a/client/src/main.tsx b/client/src/main.tsx index 8e2a7ec..36128a4 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -4,11 +4,14 @@ import { HashRouter } from 'react-router-dom'; import './i18n'; import './ui/tokens.css'; import App from './App'; +import { ToastProvider } from './ui'; createRoot(document.getElementById('root')!).render( - - - + + + + + , ); diff --git a/client/src/pages/credential/CreateCredentialPage.tsx b/client/src/pages/credential/CreateCredentialPage.tsx index f3b00da..bde2582 100644 --- a/client/src/pages/credential/CreateCredentialPage.tsx +++ b/client/src/pages/credential/CreateCredentialPage.tsx @@ -3,12 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { KeyRound, X, Save } from 'lucide-react'; import { credentials } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function CreateCredentialPage() { const { t } = useTranslation(); const navigate = useNavigate(); + const toast = useToast(); const [name, setName] = useState(''); const [data, setData] = useState(''); @@ -37,9 +38,12 @@ export function CreateCredentialPage() { setError(null); try { const credential = await credentials.create(name.trim(), data.trim() || undefined); + toast.success('Credential created'); navigate(`/credentials/${credential.id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/credential/EditCredentialPage.tsx b/client/src/pages/credential/EditCredentialPage.tsx index 83a6b6c..3b2b8b0 100644 --- a/client/src/pages/credential/EditCredentialPage.tsx +++ b/client/src/pages/credential/EditCredentialPage.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import { KeyRound, X, Save } from 'lucide-react'; import { credentials } from '../../api'; import type { Credential } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function EditCredentialPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [credential, setCredential] = useState(null); const [name, setName] = useState(''); @@ -57,9 +58,12 @@ export function EditCredentialPage() { name: name.trim(), data: data.trim() || null, }); + toast.success('Credential updated'); navigate(`/credentials/${id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/environment/CreateEnvironmentPage.tsx b/client/src/pages/environment/CreateEnvironmentPage.tsx index 62f34b6..29848e5 100644 --- a/client/src/pages/environment/CreateEnvironmentPage.tsx +++ b/client/src/pages/environment/CreateEnvironmentPage.tsx @@ -3,12 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { X, Globe, Save } from 'lucide-react'; import { environments } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function CreateEnvironmentPage() { const { t } = useTranslation(); const navigate = useNavigate(); + const toast = useToast(); const [name, setName] = useState(''); const [dataJson, setDataJson] = useState('{\n "id": "https://id.example.com"\n}'); @@ -44,9 +45,12 @@ export function CreateEnvironmentPage() { setError(null); try { const env = await environments.create(name.trim(), parsedData); + toast.success('Environment created'); navigate(`/environments/${env.id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/environment/EditEnvironmentPage.tsx b/client/src/pages/environment/EditEnvironmentPage.tsx index adcee1e..b6b9821 100644 --- a/client/src/pages/environment/EditEnvironmentPage.tsx +++ b/client/src/pages/environment/EditEnvironmentPage.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import { X, Globe, Save } from 'lucide-react'; import { environments } from '../../api'; import type { Environment } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function EditEnvironmentPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [env, setEnv] = useState(null); const [name, setName] = useState(''); @@ -64,9 +65,12 @@ export function EditEnvironmentPage() { name: name.trim(), data: parsedData, }); + toast.success('Environment updated'); navigate(`/environments/${id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/run/RunsPage.tsx b/client/src/pages/run/RunsPage.tsx index 3aaf82c..214efcb 100644 --- a/client/src/pages/run/RunsPage.tsx +++ b/client/src/pages/run/RunsPage.tsx @@ -12,6 +12,7 @@ import { Table, Timestamp, UuidBadge, + useToast, type TableColumn, } from '../../ui'; import type { BadgeVariant } from '../../ui'; @@ -37,6 +38,7 @@ export function RunsPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [scenario, setScenario] = useState(null); const [items, setItems] = useState([]); @@ -71,8 +73,13 @@ export function RunsPage() { }, [load]); const handleRun = async () => { - const run = await scenarios.run(id!); - navigate(`/scenarios/${id}/runs/${run.id}`); + try { + const run = await scenarios.run(id!); + toast.success('Scenario run started'); + navigate(`/scenarios/${id}/runs/${run.id}`); + } catch (err) { + toast.error((err as Error).message); + } }; const columns: TableColumn[] = [ diff --git a/client/src/pages/scenario/CreateScenarioPage.tsx b/client/src/pages/scenario/CreateScenarioPage.tsx index d348799..8cae2d6 100644 --- a/client/src/pages/scenario/CreateScenarioPage.tsx +++ b/client/src/pages/scenario/CreateScenarioPage.tsx @@ -3,12 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { X, Save, ClipboardList } from 'lucide-react'; import { scenarios } from '../../api'; -import { Breadcrumbs, Button, Card, Input } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, useToast } from '../../ui'; import styles from '../Page.module.css'; export function CreateScenarioPage() { const { t } = useTranslation(); const navigate = useNavigate(); + const toast = useToast(); const [name, setName] = useState(''); const [nameError, setNameError] = useState(''); @@ -25,9 +26,12 @@ export function CreateScenarioPage() { setError(null); try { const scenario = await scenarios.create(name.trim()); + toast.success('Scenario created'); navigate(`/scenarios/${scenario.id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/scenario/CreateStepPage.tsx b/client/src/pages/scenario/CreateStepPage.tsx index c93ce64..800f287 100644 --- a/client/src/pages/scenario/CreateStepPage.tsx +++ b/client/src/pages/scenario/CreateStepPage.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import { X, Save, ClipboardList } from 'lucide-react'; import { scenarios, steps } from '../../api'; import type { Scenario } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function CreateStepPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [scenario, setScenario] = useState(null); const [title, setTitle] = useState(''); @@ -37,9 +38,12 @@ export function CreateStepPage() { execCode: execCode.trim() || undefined, validateCode: validateCode.trim() || undefined, }); + toast.success('Step created'); navigate(`/scenarios/${id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/scenario/EditScenarioPage.tsx b/client/src/pages/scenario/EditScenarioPage.tsx index 542b2cf..9393399 100644 --- a/client/src/pages/scenario/EditScenarioPage.tsx +++ b/client/src/pages/scenario/EditScenarioPage.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import { X, Save, ClipboardList } from 'lucide-react'; import { scenarios } from '../../api'; import type { Scenario } from '../../api'; -import { Breadcrumbs, Button, Card, Input } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, useToast } from '../../ui'; import styles from '../Page.module.css'; export function EditScenarioPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [scenario, setScenario] = useState(null); const [name, setName] = useState(''); @@ -41,9 +42,12 @@ export function EditScenarioPage() { setError(null); try { await scenarios.update(id!, { name: name.trim() }); + toast.success('Scenario updated'); navigate(`/scenarios/${id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/scenario/EditStepPage.tsx b/client/src/pages/scenario/EditStepPage.tsx index 6173b14..4bb56cb 100644 --- a/client/src/pages/scenario/EditStepPage.tsx +++ b/client/src/pages/scenario/EditStepPage.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import { X, Save, ClipboardList } from 'lucide-react'; import { scenarios, steps } from '../../api'; import type { Scenario, ScenarioStep } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function EditStepPage() { const { t } = useTranslation(); const { id, stepId } = useParams<{ id: string; stepId: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [scenario, setScenario] = useState(null); const [step, setStep] = useState(null); @@ -45,9 +46,12 @@ export function EditStepPage() { execCode: execCode.trim() || undefined, validateCode: validateCode.trim() || undefined, }); + toast.success('Step updated'); navigate(`/scenarios/${id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/scenario/ScenarioDetailPage.tsx b/client/src/pages/scenario/ScenarioDetailPage.tsx index 501177e..1c0eef0 100644 --- a/client/src/pages/scenario/ScenarioDetailPage.tsx +++ b/client/src/pages/scenario/ScenarioDetailPage.tsx @@ -16,6 +16,7 @@ import { Table, Timestamp, UuidBadge, + useToast, type TableColumn, } from '../../ui'; import styles from '../Page.module.css'; @@ -24,6 +25,7 @@ export function ScenarioDetailPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [scenario, setScenario] = useState<(Scenario & { steps: ScenarioStep[] }) | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -66,8 +68,13 @@ export function ScenarioDetailPage() { const handleRun = async () => { if (!scenario) return; - const run = await scenarios.run(scenario.id); - navigate(`/scenarios/${id}/runs/${run.id}`); + try { + const run = await scenarios.run(scenario.id); + toast.success('Scenario run started'); + navigate(`/scenarios/${id}/runs/${run.id}`); + } catch (err) { + toast.error((err as Error).message); + } }; const handleDelete = async () => { diff --git a/client/src/pages/scenario/ScenariosPage.tsx b/client/src/pages/scenario/ScenariosPage.tsx index 18f9679..2576228 100644 --- a/client/src/pages/scenario/ScenariosPage.tsx +++ b/client/src/pages/scenario/ScenariosPage.tsx @@ -5,12 +5,13 @@ import { Play, Plus, Trash2, Download, ClipboardList } from 'lucide-react'; import { parse as yamlParse } from 'yaml'; import { scenarios } from '../../api'; import type { Scenario } from '../../api'; -import { Breadcrumbs, Button, Table, type TableColumn, Timestamp, UuidBadge } from '../../ui'; +import { Breadcrumbs, Button, Table, type TableColumn, Timestamp, UuidBadge, useToast } from '../../ui'; import styles from '../Page.module.css'; export function ScenariosPage() { const { t } = useTranslation(); const navigate = useNavigate(); + const toast = useToast(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -30,8 +31,13 @@ export function ScenariosPage() { }, [load]); const handleRun = async (id: string) => { - const run = await scenarios.run(id); - navigate(`/scenarios/${id}/runs/${run.id}`); + try { + const run = await scenarios.run(id); + toast.success('Scenario run started'); + navigate(`/scenarios/${id}/runs/${run.id}`); + } catch (err) { + toast.error((err as Error).message); + } }; const handleDelete = async (id: string) => { diff --git a/client/src/pages/snippet/CreateSnippetPage.tsx b/client/src/pages/snippet/CreateSnippetPage.tsx index d7b5a8f..5c4113a 100644 --- a/client/src/pages/snippet/CreateSnippetPage.tsx +++ b/client/src/pages/snippet/CreateSnippetPage.tsx @@ -3,12 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { X, Save, Braces } from 'lucide-react'; import { snippets } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function CreateSnippetPage() { const { t } = useTranslation(); const navigate = useNavigate(); + const toast = useToast(); const [name, setName] = useState(''); const [description, setDescription] = useState(''); @@ -38,9 +39,12 @@ export function CreateSnippetPage() { description: description.trim() || undefined, code: code.trim(), }); + toast.success('Snippet created'); navigate(`/snippets/${snippet.id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/pages/snippet/EditSnippetPage.tsx b/client/src/pages/snippet/EditSnippetPage.tsx index 37d375e..68bbe4a 100644 --- a/client/src/pages/snippet/EditSnippetPage.tsx +++ b/client/src/pages/snippet/EditSnippetPage.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import { X, Save, Braces } from 'lucide-react'; import { snippets } from '../../api'; import type { Snippet } from '../../api'; -import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui'; import styles from '../Page.module.css'; export function EditSnippetPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const toast = useToast(); const [snippet, setSnippet] = useState(null); const [name, setName] = useState(''); @@ -56,9 +57,12 @@ export function EditSnippetPage() { description: description.trim() || undefined, code: code.trim(), }); + toast.success('Snippet updated'); navigate(`/snippets/${id}`); } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + setError(message); + toast.error(message); } finally { setSaving(false); } diff --git a/client/src/ui/Toast/ToastProvider.module.css b/client/src/ui/Toast/ToastProvider.module.css new file mode 100644 index 0000000..5fa0eb4 --- /dev/null +++ b/client/src/ui/Toast/ToastProvider.module.css @@ -0,0 +1,87 @@ +.viewport { + position: fixed; + top: var(--space-4); + right: var(--space-4); + z-index: 2000; + display: flex; + flex-direction: column; + gap: var(--space-2); + width: min(380px, calc(100vw - (var(--space-4) * 2))); + pointer-events: none; +} + +.toast { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-3); + padding: var(--space-3) var(--space-3); + border-radius: var(--radius-md); + border: var(--border-width) solid var(--color-border); + background: var(--color-bg-subtle); + box-shadow: var(--shadow-md); + pointer-events: auto; + opacity: 1; + transform: translateX(0) scale(1); + transition: opacity 100ms ease, transform 100ms ease; +} + +.leaving { + opacity: 0; + transform: translateX(8px) scale(0.98); + pointer-events: none; +} + +.content { + display: flex; + align-items: flex-start; + gap: var(--space-2); + min-width: 0; +} + +.icon { + margin-top: 1px; + flex-shrink: 0; +} + +.message { + margin: 0; + font-size: var(--font-size-sm); + line-height: 1.4; + color: var(--color-text); + overflow-wrap: anywhere; +} + +.close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + background: transparent; + border-radius: var(--radius-sm); + color: var(--color-text-muted); + cursor: pointer; + flex-shrink: 0; +} + +.close:hover { + background: var(--color-secondary); + color: var(--color-secondary-fg); +} + +.success { + border-color: color-mix(in srgb, var(--color-primary) 50%, var(--color-border)); + background: var(--color-success-bg); +} + +.error { + border-color: color-mix(in srgb, var(--color-danger) 50%, var(--color-border)); + background: var(--color-error-bg); +} + +.info { + border-color: color-mix(in srgb, var(--color-secondary-fg) 35%, var(--color-border)); + background: var(--color-info-bg); +} diff --git a/client/src/ui/Toast/ToastProvider.tsx b/client/src/ui/Toast/ToastProvider.tsx new file mode 100644 index 0000000..23c3749 --- /dev/null +++ b/client/src/ui/Toast/ToastProvider.tsx @@ -0,0 +1,101 @@ +import { createContext, useCallback, useContext, useMemo, useState } from 'react'; +import { AlertCircle, CheckCircle2, Info, X } from 'lucide-react'; +import styles from './ToastProvider.module.css'; + +type ToastVariant = 'success' | 'error' | 'info'; + +interface ToastItem { + id: number; + message: string; + variant: ToastVariant; + closing: boolean; +} + +interface ToastApi { + show: (message: string, variant?: ToastVariant) => void; + success: (message: string) => void; + error: (message: string) => void; + info: (message: string) => void; +} + +const ToastContext = createContext(null); + +const ICONS: Record = { + success: , + error: , + info: , +}; + +export interface ToastProviderProps { + children: React.ReactNode; + durationMs?: number; +} + +export function ToastProvider({ children, durationMs = 3500 }: ToastProviderProps) { + const EXIT_ANIMATION_MS = 100; + const [items, setItems] = useState([]); + + const remove = useCallback((id: number) => { + setItems((prev) => prev.filter((item) => item.id !== id)); + }, []); + + const dismiss = useCallback((id: number) => { + setItems((prev) => + prev.map((item) => + item.id === id ? { ...item, closing: true } : item, + ), + ); + window.setTimeout(() => remove(id), EXIT_ANIMATION_MS); + }, [remove]); + + const show = useCallback((message: string, variant: ToastVariant = 'info') => { + const id = Date.now() + Math.floor(Math.random() * 1000); + setItems((prev) => [...prev, { id, message, variant, closing: false }]); + window.setTimeout(() => dismiss(id), durationMs); + }, [dismiss, durationMs]); + + const api = useMemo(() => ({ + show, + success: (message: string) => show(message, 'success'), + error: (message: string) => show(message, 'error'), + info: (message: string) => show(message, 'info'), + }), [show]); + + return ( + + {children} +
+ {items.map((item) => ( +
+
+ +

{item.message}

+
+ +
+ ))} +
+
+ ); +} + +export function useToast(): ToastApi { + const ctx = useContext(ToastContext); + if (!ctx) { + throw new Error('useToast must be used within ToastProvider'); + } + return ctx; +} diff --git a/client/src/ui/index.ts b/client/src/ui/index.ts index 1d54436..5d0c9e7 100644 --- a/client/src/ui/index.ts +++ b/client/src/ui/index.ts @@ -27,6 +27,9 @@ export type { BreadcrumbsProps, BreadcrumbItem } from './Breadcrumbs/Breadcrumbs export { ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher'; +export { ToastProvider, useToast } from './Toast/ToastProvider'; +export type { ToastProviderProps } from './Toast/ToastProvider'; + export { Timestamp } from './Timestamp/Timestamp'; export type { TimestampProps } from './Timestamp/Timestamp'; diff --git a/client/tsconfig.tsbuildinfo b/client/tsconfig.tsbuildinfo index 76114a2..59b4829 100644 --- a/client/tsconfig.tsbuildinfo +++ b/client/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/declarations.d.ts","./src/main.tsx","./src/api/client.ts","./src/api/index.ts","./src/api/types.ts","./src/hooks/useTheme.ts","./src/i18n/index.ts","./src/lib/hljs.ts","./src/pages/credential/CreateCredentialPage.tsx","./src/pages/credential/CredentialDetailPage.tsx","./src/pages/credential/CredentialsPage.tsx","./src/pages/credential/EditCredentialPage.tsx","./src/pages/environment/CreateEnvironmentPage.tsx","./src/pages/environment/EditEnvironmentPage.tsx","./src/pages/environment/EnvironmentDetailPage.tsx","./src/pages/environment/EnvironmentsPage.tsx","./src/pages/run/AllRunsPage.tsx","./src/pages/run/RunDetailPage.tsx","./src/pages/run/RunsPage.tsx","./src/pages/scenario/CreateScenarioPage.tsx","./src/pages/scenario/CreateStepPage.tsx","./src/pages/scenario/EditScenarioPage.tsx","./src/pages/scenario/EditStepPage.tsx","./src/pages/scenario/ScenarioDetailPage.tsx","./src/pages/scenario/ScenariosPage.tsx","./src/pages/session/SessionDetailPage.tsx","./src/pages/session/SessionsPage.tsx","./src/pages/snippet/CreateSnippetPage.tsx","./src/pages/snippet/EditSnippetPage.tsx","./src/pages/snippet/SnippetDetailPage.tsx","./src/pages/snippet/SnippetsPage.tsx","./src/ui/index.ts","./src/ui/AutoRefreshIndicator/AutoRefreshIndicator.tsx","./src/ui/Badge/Badge.tsx","./src/ui/Breadcrumbs/Breadcrumbs.tsx","./src/ui/Button/Button.tsx","./src/ui/Card/Card.tsx","./src/ui/CodeBlock/CodeBlock.tsx","./src/ui/CodeEditor/CodeEditor.tsx","./src/ui/ContextMenu/ContextMenu.tsx","./src/ui/DescriptionList/DescriptionList.tsx","./src/ui/Input/Input.tsx","./src/ui/Notification/Notification.tsx","./src/ui/Pagination/Pagination.tsx","./src/ui/Search/Search.tsx","./src/ui/Select/Select.tsx","./src/ui/SidePanel/SidePanel.tsx","./src/ui/Table/Table.tsx","./src/ui/Textarea/Textarea.tsx","./src/ui/ThemeSwitcher/ThemeSwitcher.tsx","./src/ui/Timestamp/Timestamp.tsx","./src/ui/UuidBadge/UuidBadge.tsx"],"version":"5.8.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/declarations.d.ts","./src/main.tsx","./src/api/client.ts","./src/api/index.ts","./src/api/types.ts","./src/hooks/useTheme.ts","./src/i18n/index.ts","./src/lib/hljs.ts","./src/pages/credential/CreateCredentialPage.tsx","./src/pages/credential/CredentialDetailPage.tsx","./src/pages/credential/CredentialsPage.tsx","./src/pages/credential/EditCredentialPage.tsx","./src/pages/environment/CreateEnvironmentPage.tsx","./src/pages/environment/EditEnvironmentPage.tsx","./src/pages/environment/EnvironmentDetailPage.tsx","./src/pages/environment/EnvironmentsPage.tsx","./src/pages/run/AllRunsPage.tsx","./src/pages/run/RunDetailPage.tsx","./src/pages/run/RunsPage.tsx","./src/pages/scenario/CreateScenarioPage.tsx","./src/pages/scenario/CreateStepPage.tsx","./src/pages/scenario/EditScenarioPage.tsx","./src/pages/scenario/EditStepPage.tsx","./src/pages/scenario/ScenarioDetailPage.tsx","./src/pages/scenario/ScenariosPage.tsx","./src/pages/session/SessionDetailPage.tsx","./src/pages/session/SessionsPage.tsx","./src/pages/snippet/CreateSnippetPage.tsx","./src/pages/snippet/EditSnippetPage.tsx","./src/pages/snippet/SnippetDetailPage.tsx","./src/pages/snippet/SnippetsPage.tsx","./src/ui/index.ts","./src/ui/AutoRefreshIndicator/AutoRefreshIndicator.tsx","./src/ui/Badge/Badge.tsx","./src/ui/Breadcrumbs/Breadcrumbs.tsx","./src/ui/Button/Button.tsx","./src/ui/Card/Card.tsx","./src/ui/CodeBlock/CodeBlock.tsx","./src/ui/CodeEditor/CodeEditor.tsx","./src/ui/ContextMenu/ContextMenu.tsx","./src/ui/DescriptionList/DescriptionList.tsx","./src/ui/Input/Input.tsx","./src/ui/Notification/Notification.tsx","./src/ui/Pagination/Pagination.tsx","./src/ui/Search/Search.tsx","./src/ui/Select/Select.tsx","./src/ui/SidePanel/SidePanel.tsx","./src/ui/Table/Table.tsx","./src/ui/Textarea/Textarea.tsx","./src/ui/ThemeSwitcher/ThemeSwitcher.tsx","./src/ui/Timestamp/Timestamp.tsx","./src/ui/Toast/ToastProvider.tsx","./src/ui/UuidBadge/UuidBadge.tsx"],"version":"5.8.3"} \ No newline at end of file