flock-core 0.5.0b50__py3-none-any.whl → 0.5.0b51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/dashboard/launcher.py +1 -1
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/RECORD +116 -6
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Phase 11: Build Info Display
|
|
5
|
+
*
|
|
6
|
+
* Shows build hash and timestamp in UI corner for deployment verification.
|
|
7
|
+
* Makes it easy to confirm which version of code is running.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const BuildInfo: React.FC = () => {
|
|
11
|
+
const buildHash = typeof __BUILD_HASH__ !== 'undefined' ? __BUILD_HASH__ : 'dev';
|
|
12
|
+
const buildTime = typeof __BUILD_TIMESTAMP__ !== 'undefined' ? __BUILD_TIMESTAMP__ : 'unknown';
|
|
13
|
+
|
|
14
|
+
const formattedTime = buildTime !== 'unknown'
|
|
15
|
+
? new Date(buildTime).toLocaleString('en-US', {
|
|
16
|
+
month: 'short',
|
|
17
|
+
day: 'numeric',
|
|
18
|
+
hour: '2-digit',
|
|
19
|
+
minute: '2-digit',
|
|
20
|
+
})
|
|
21
|
+
: 'unknown';
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
style={{
|
|
26
|
+
fontSize: '10px',
|
|
27
|
+
color: 'var(--color-text-tertiary, #6b7280)',
|
|
28
|
+
fontFamily: 'monospace',
|
|
29
|
+
lineHeight: '1.4',
|
|
30
|
+
opacity: 0.7,
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<div>Build: {buildHash}</div>
|
|
34
|
+
<div>{formattedTime}</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default BuildInfo;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* Empty State Component */
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
padding: var(--space-layout-xl);
|
|
9
|
+
text-align: center;
|
|
10
|
+
max-width: 400px;
|
|
11
|
+
margin: 0 auto;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.icon {
|
|
15
|
+
margin-bottom: var(--spacing-6);
|
|
16
|
+
color: var(--color-text-muted);
|
|
17
|
+
opacity: 0.6;
|
|
18
|
+
animation: fadeInScale var(--duration-slow) var(--ease-smooth);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.svgIcon {
|
|
22
|
+
display: block;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.title {
|
|
26
|
+
font-size: var(--font-size-h3);
|
|
27
|
+
font-weight: var(--font-weight-semibold);
|
|
28
|
+
color: var(--color-text-primary);
|
|
29
|
+
margin: 0 0 var(--spacing-3) 0;
|
|
30
|
+
line-height: var(--line-height-tight);
|
|
31
|
+
animation: fadeIn var(--duration-slow) var(--ease-smooth);
|
|
32
|
+
animation-delay: calc(var(--duration-fast));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.description {
|
|
36
|
+
font-size: var(--font-size-body-sm);
|
|
37
|
+
color: var(--color-text-secondary);
|
|
38
|
+
line-height: var(--line-height-relaxed);
|
|
39
|
+
margin: 0 0 var(--spacing-6) 0;
|
|
40
|
+
animation: fadeIn var(--duration-slow) var(--ease-smooth);
|
|
41
|
+
animation-delay: calc(var(--duration-fast) * 2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.action {
|
|
45
|
+
padding: var(--space-component-sm) var(--space-component-lg);
|
|
46
|
+
font-size: var(--font-size-body-sm);
|
|
47
|
+
font-weight: var(--font-weight-semibold);
|
|
48
|
+
color: var(--color-text-on-primary);
|
|
49
|
+
background: var(--color-primary-500);
|
|
50
|
+
border: none;
|
|
51
|
+
border-radius: var(--radius-md);
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
box-shadow: var(--shadow-sm);
|
|
54
|
+
transition: var(--transition-colors), var(--transition-shadow);
|
|
55
|
+
animation: fadeInUp var(--duration-slow) var(--ease-smooth);
|
|
56
|
+
animation-delay: calc(var(--duration-fast) * 3);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.action:hover {
|
|
60
|
+
background: var(--color-primary-600);
|
|
61
|
+
box-shadow: var(--shadow-md);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.action:active {
|
|
65
|
+
background: var(--color-primary-700);
|
|
66
|
+
box-shadow: var(--shadow-xs);
|
|
67
|
+
transform: translateY(1px);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.action:focus-visible {
|
|
71
|
+
outline: none;
|
|
72
|
+
box-shadow: var(--shadow-glow-primary);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Animations */
|
|
76
|
+
@keyframes fadeIn {
|
|
77
|
+
from {
|
|
78
|
+
opacity: 0;
|
|
79
|
+
}
|
|
80
|
+
to {
|
|
81
|
+
opacity: 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@keyframes fadeInUp {
|
|
86
|
+
from {
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transform: translateY(8px);
|
|
89
|
+
}
|
|
90
|
+
to {
|
|
91
|
+
opacity: 1;
|
|
92
|
+
transform: translateY(0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@keyframes fadeInScale {
|
|
97
|
+
from {
|
|
98
|
+
opacity: 0;
|
|
99
|
+
transform: scale(0.9);
|
|
100
|
+
}
|
|
101
|
+
to {
|
|
102
|
+
opacity: 0.6;
|
|
103
|
+
transform: scale(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Respect reduced motion preference */
|
|
108
|
+
@media (prefers-reduced-motion: reduce) {
|
|
109
|
+
.icon,
|
|
110
|
+
.title,
|
|
111
|
+
.description,
|
|
112
|
+
.action {
|
|
113
|
+
animation: none;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './EmptyState.module.css';
|
|
3
|
+
|
|
4
|
+
interface EmptyStateProps {
|
|
5
|
+
icon?: React.ReactNode;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
action?: {
|
|
9
|
+
label: string;
|
|
10
|
+
onClick: () => void;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Elegant empty state component with optional CTA
|
|
16
|
+
*/
|
|
17
|
+
export const EmptyState: React.FC<EmptyStateProps> = ({
|
|
18
|
+
icon,
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
action,
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<div className={styles.container}>
|
|
25
|
+
{icon && <div className={styles.icon}>{icon}</div>}
|
|
26
|
+
|
|
27
|
+
<h3 className={styles.title}>{title}</h3>
|
|
28
|
+
|
|
29
|
+
{description && (
|
|
30
|
+
<p className={styles.description}>{description}</p>
|
|
31
|
+
)}
|
|
32
|
+
|
|
33
|
+
{action && (
|
|
34
|
+
<button
|
|
35
|
+
className={styles.action}
|
|
36
|
+
onClick={action.onClick}
|
|
37
|
+
>
|
|
38
|
+
{action.label}
|
|
39
|
+
</button>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Icon components for common empty states
|
|
47
|
+
*/
|
|
48
|
+
export const EmptyGraphIcon: React.FC = () => (
|
|
49
|
+
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" className={styles.svgIcon}>
|
|
50
|
+
<circle
|
|
51
|
+
cx="20"
|
|
52
|
+
cy="32"
|
|
53
|
+
r="8"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
strokeWidth="2"
|
|
56
|
+
fill="none"
|
|
57
|
+
/>
|
|
58
|
+
<circle
|
|
59
|
+
cx="44"
|
|
60
|
+
cy="20"
|
|
61
|
+
r="8"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
strokeWidth="2"
|
|
64
|
+
fill="none"
|
|
65
|
+
/>
|
|
66
|
+
<circle
|
|
67
|
+
cx="44"
|
|
68
|
+
cy="44"
|
|
69
|
+
r="8"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
strokeWidth="2"
|
|
72
|
+
fill="none"
|
|
73
|
+
/>
|
|
74
|
+
<line
|
|
75
|
+
x1="28"
|
|
76
|
+
y1="32"
|
|
77
|
+
x2="36"
|
|
78
|
+
y2="24"
|
|
79
|
+
stroke="currentColor"
|
|
80
|
+
strokeWidth="2"
|
|
81
|
+
strokeDasharray="4 4"
|
|
82
|
+
/>
|
|
83
|
+
<line
|
|
84
|
+
x1="28"
|
|
85
|
+
y1="32"
|
|
86
|
+
x2="36"
|
|
87
|
+
y2="40"
|
|
88
|
+
stroke="currentColor"
|
|
89
|
+
strokeWidth="2"
|
|
90
|
+
strokeDasharray="4 4"
|
|
91
|
+
/>
|
|
92
|
+
</svg>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export const EmptyMessageIcon: React.FC = () => (
|
|
96
|
+
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" className={styles.svgIcon}>
|
|
97
|
+
<rect
|
|
98
|
+
x="8"
|
|
99
|
+
y="16"
|
|
100
|
+
width="48"
|
|
101
|
+
height="32"
|
|
102
|
+
rx="4"
|
|
103
|
+
stroke="currentColor"
|
|
104
|
+
strokeWidth="2"
|
|
105
|
+
fill="none"
|
|
106
|
+
/>
|
|
107
|
+
<line
|
|
108
|
+
x1="16"
|
|
109
|
+
y1="26"
|
|
110
|
+
x2="40"
|
|
111
|
+
y2="26"
|
|
112
|
+
stroke="currentColor"
|
|
113
|
+
strokeWidth="2"
|
|
114
|
+
strokeLinecap="round"
|
|
115
|
+
opacity="0.5"
|
|
116
|
+
/>
|
|
117
|
+
<line
|
|
118
|
+
x1="16"
|
|
119
|
+
y1="34"
|
|
120
|
+
x2="48"
|
|
121
|
+
y2="34"
|
|
122
|
+
stroke="currentColor"
|
|
123
|
+
strokeWidth="2"
|
|
124
|
+
strokeLinecap="round"
|
|
125
|
+
opacity="0.5"
|
|
126
|
+
/>
|
|
127
|
+
</svg>
|
|
128
|
+
);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/* Error Boundary Component */
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
min-height: 400px;
|
|
8
|
+
padding: var(--space-layout-md);
|
|
9
|
+
background: var(--color-bg-base);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.content {
|
|
13
|
+
max-width: 500px;
|
|
14
|
+
text-align: center;
|
|
15
|
+
padding: var(--space-layout-lg);
|
|
16
|
+
background: var(--color-bg-surface);
|
|
17
|
+
border: var(--border-subtle);
|
|
18
|
+
border-radius: var(--radius-xl);
|
|
19
|
+
box-shadow: var(--shadow-lg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.icon {
|
|
23
|
+
color: var(--color-error);
|
|
24
|
+
margin-bottom: var(--spacing-6);
|
|
25
|
+
animation: shake 0.5s var(--ease-smooth);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.title {
|
|
29
|
+
font-size: var(--font-size-h2);
|
|
30
|
+
font-weight: var(--font-weight-bold);
|
|
31
|
+
color: var(--color-text-primary);
|
|
32
|
+
margin: 0 0 var(--spacing-4) 0;
|
|
33
|
+
line-height: var(--line-height-tight);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.description {
|
|
37
|
+
font-size: var(--font-size-body);
|
|
38
|
+
color: var(--color-text-secondary);
|
|
39
|
+
line-height: var(--line-height-relaxed);
|
|
40
|
+
margin: 0 0 var(--spacing-6) 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.details {
|
|
44
|
+
margin: var(--spacing-6) 0;
|
|
45
|
+
text-align: left;
|
|
46
|
+
background: var(--color-bg-base);
|
|
47
|
+
border: var(--border-default);
|
|
48
|
+
border-radius: var(--radius-md);
|
|
49
|
+
padding: var(--space-component-md);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.summary {
|
|
53
|
+
font-size: var(--font-size-body-sm);
|
|
54
|
+
font-weight: var(--font-weight-semibold);
|
|
55
|
+
color: var(--color-text-secondary);
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
user-select: none;
|
|
58
|
+
padding: var(--spacing-2);
|
|
59
|
+
margin: calc(var(--space-component-md) * -1);
|
|
60
|
+
margin-bottom: var(--spacing-3);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.summary:hover {
|
|
64
|
+
color: var(--color-text-primary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.errorMessage {
|
|
68
|
+
font-size: var(--font-size-body-sm);
|
|
69
|
+
color: var(--color-error-light);
|
|
70
|
+
margin-bottom: var(--spacing-3);
|
|
71
|
+
line-height: var(--line-height-relaxed);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.stackTrace {
|
|
75
|
+
font-family: var(--font-family-mono);
|
|
76
|
+
font-size: var(--font-size-caption);
|
|
77
|
+
color: var(--color-text-tertiary);
|
|
78
|
+
background: var(--color-bg-elevated);
|
|
79
|
+
padding: var(--space-component-sm);
|
|
80
|
+
border-radius: var(--radius-sm);
|
|
81
|
+
overflow-x: auto;
|
|
82
|
+
white-space: pre-wrap;
|
|
83
|
+
word-break: break-word;
|
|
84
|
+
max-height: 200px;
|
|
85
|
+
overflow-y: auto;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.actions {
|
|
89
|
+
display: flex;
|
|
90
|
+
gap: var(--gap-md);
|
|
91
|
+
justify-content: center;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.primaryButton {
|
|
95
|
+
padding: var(--space-component-sm) var(--space-component-lg);
|
|
96
|
+
font-size: var(--font-size-body-sm);
|
|
97
|
+
font-weight: var(--font-weight-semibold);
|
|
98
|
+
color: var(--color-text-on-primary);
|
|
99
|
+
background: var(--color-primary-500);
|
|
100
|
+
border: none;
|
|
101
|
+
border-radius: var(--radius-md);
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
box-shadow: var(--shadow-sm);
|
|
104
|
+
transition: var(--transition-colors), var(--transition-shadow);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.primaryButton:hover {
|
|
108
|
+
background: var(--color-primary-600);
|
|
109
|
+
box-shadow: var(--shadow-md);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.primaryButton:active {
|
|
113
|
+
background: var(--color-primary-700);
|
|
114
|
+
box-shadow: var(--shadow-xs);
|
|
115
|
+
transform: translateY(1px);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.primaryButton:focus-visible {
|
|
119
|
+
outline: none;
|
|
120
|
+
box-shadow: var(--shadow-glow-primary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.secondaryButton {
|
|
124
|
+
padding: var(--space-component-sm) var(--space-component-lg);
|
|
125
|
+
font-size: var(--font-size-body-sm);
|
|
126
|
+
font-weight: var(--font-weight-semibold);
|
|
127
|
+
color: var(--color-text-secondary);
|
|
128
|
+
background: transparent;
|
|
129
|
+
border: var(--border-default);
|
|
130
|
+
border-radius: var(--radius-md);
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: var(--transition-colors);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.secondaryButton:hover {
|
|
136
|
+
background: var(--color-bg-overlay);
|
|
137
|
+
color: var(--color-text-primary);
|
|
138
|
+
border-color: var(--color-border-strong);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.secondaryButton:active {
|
|
142
|
+
transform: translateY(1px);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.secondaryButton:focus-visible {
|
|
146
|
+
outline: none;
|
|
147
|
+
border-color: var(--color-border-focus);
|
|
148
|
+
box-shadow: var(--shadow-glow-primary);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Animations */
|
|
152
|
+
@keyframes shake {
|
|
153
|
+
0%, 100% {
|
|
154
|
+
transform: translateX(0);
|
|
155
|
+
}
|
|
156
|
+
10%, 30%, 50%, 70%, 90% {
|
|
157
|
+
transform: translateX(-4px);
|
|
158
|
+
}
|
|
159
|
+
20%, 40%, 60%, 80% {
|
|
160
|
+
transform: translateX(4px);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Respect reduced motion preference */
|
|
165
|
+
@media (prefers-reduced-motion: reduce) {
|
|
166
|
+
.icon {
|
|
167
|
+
animation: none;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Component, ErrorInfo, ReactNode } from 'react';
|
|
2
|
+
import styles from './ErrorBoundary.module.css';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
fallback?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface State {
|
|
10
|
+
hasError: boolean;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
errorInfo: ErrorInfo | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Error Boundary component to catch and display React errors gracefully
|
|
17
|
+
*/
|
|
18
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
19
|
+
constructor(props: Props) {
|
|
20
|
+
super(props);
|
|
21
|
+
this.state = {
|
|
22
|
+
hasError: false,
|
|
23
|
+
error: null,
|
|
24
|
+
errorInfo: null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static getDerivedStateFromError(error: Error): Partial<State> {
|
|
29
|
+
return { hasError: true, error };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
33
|
+
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
|
34
|
+
this.setState({ errorInfo });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
handleReset = () => {
|
|
38
|
+
this.setState({
|
|
39
|
+
hasError: false,
|
|
40
|
+
error: null,
|
|
41
|
+
errorInfo: null,
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
handleReload = () => {
|
|
46
|
+
window.location.reload();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
render() {
|
|
50
|
+
if (this.state.hasError) {
|
|
51
|
+
if (this.props.fallback) {
|
|
52
|
+
return this.props.fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={styles.container} role="alert">
|
|
57
|
+
<div className={styles.content}>
|
|
58
|
+
<div className={styles.icon}>
|
|
59
|
+
<svg width="64" height="64" viewBox="0 0 64 64" fill="none">
|
|
60
|
+
<circle
|
|
61
|
+
cx="32"
|
|
62
|
+
cy="32"
|
|
63
|
+
r="28"
|
|
64
|
+
stroke="currentColor"
|
|
65
|
+
strokeWidth="2"
|
|
66
|
+
fill="none"
|
|
67
|
+
/>
|
|
68
|
+
<path
|
|
69
|
+
d="M32 20v16M32 44v.5"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
strokeWidth="3"
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
/>
|
|
74
|
+
</svg>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<h2 className={styles.title}>Something went wrong</h2>
|
|
78
|
+
|
|
79
|
+
<p className={styles.description}>
|
|
80
|
+
An unexpected error occurred. You can try resetting the component or reloading the page.
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
{this.state.error && import.meta.env.DEV && (
|
|
84
|
+
<details className={styles.details}>
|
|
85
|
+
<summary className={styles.summary}>Error Details</summary>
|
|
86
|
+
<div className={styles.errorMessage}>
|
|
87
|
+
<strong>Error:</strong> {this.state.error.toString()}
|
|
88
|
+
</div>
|
|
89
|
+
{this.state.errorInfo && (
|
|
90
|
+
<pre className={styles.stackTrace}>
|
|
91
|
+
{this.state.errorInfo.componentStack}
|
|
92
|
+
</pre>
|
|
93
|
+
)}
|
|
94
|
+
</details>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
<div className={styles.actions}>
|
|
98
|
+
<button
|
|
99
|
+
className={styles.primaryButton}
|
|
100
|
+
onClick={this.handleReset}
|
|
101
|
+
>
|
|
102
|
+
Try Again
|
|
103
|
+
</button>
|
|
104
|
+
<button
|
|
105
|
+
className={styles.secondaryButton}
|
|
106
|
+
onClick={this.handleReload}
|
|
107
|
+
>
|
|
108
|
+
Reload Page
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return this.props.children;
|
|
117
|
+
}
|
|
118
|
+
}
|