yadflow 2.6.0 → 2.7.0

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +4 -7
  2. package/README.md +23 -2
  3. package/bin/yad.mjs +22 -1
  4. package/cli/docs.mjs +298 -0
  5. package/cli/manifest.mjs +6 -1
  6. package/docs/index.html +4 -4
  7. package/package.json +2 -2
  8. package/skills/sdlc/config.yaml +19 -0
  9. package/skills/sdlc/install.sh +1 -1
  10. package/skills/sdlc/module-help.csv +4 -0
  11. package/skills/yad-connect-docs/SKILL.md +132 -0
  12. package/skills/yad-connect-docs/references/docs-registry.md +74 -0
  13. package/skills/yad-docs/SKILL.md +159 -0
  14. package/skills/yad-docs/references/data-mapping.md +75 -0
  15. package/skills/yad-docs/references/theme-map.md +69 -0
  16. package/skills/yad-docs/templates/app/README.md +31 -0
  17. package/skills/yad-docs/templates/app/eslint.config.js +23 -0
  18. package/skills/yad-docs/templates/app/index.html +17 -0
  19. package/skills/yad-docs/templates/app/package-lock.json +4030 -0
  20. package/skills/yad-docs/templates/app/package.json +35 -0
  21. package/skills/yad-docs/templates/app/public/favicon.svg +28 -0
  22. package/skills/yad-docs/templates/app/public/logo.svg +39 -0
  23. package/skills/yad-docs/templates/app/public/vite.svg +1 -0
  24. package/skills/yad-docs/templates/app/src/App.tsx +98 -0
  25. package/skills/yad-docs/templates/app/src/components/Auth/LoginPage.tsx +101 -0
  26. package/skills/yad-docs/templates/app/src/components/Canvas/AnimatedMessage.tsx +101 -0
  27. package/skills/yad-docs/templates/app/src/components/Canvas/ConnectionLine.tsx +90 -0
  28. package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +216 -0
  29. package/skills/yad-docs/templates/app/src/components/Canvas/SystemComponent.tsx +153 -0
  30. package/skills/yad-docs/templates/app/src/components/Controls/PlaybackBar.tsx +284 -0
  31. package/skills/yad-docs/templates/app/src/components/Controls/StepDetail.tsx +167 -0
  32. package/skills/yad-docs/templates/app/src/components/DetailPanel/HandlerLogicSnippet.tsx +41 -0
  33. package/skills/yad-docs/templates/app/src/components/DetailPanel/RequestPayloadPreview.tsx +46 -0
  34. package/skills/yad-docs/templates/app/src/components/DetailPanel/RightPanel.tsx +88 -0
  35. package/skills/yad-docs/templates/app/src/components/DetailPanel/StatusCard.tsx +76 -0
  36. package/skills/yad-docs/templates/app/src/components/DetailPanel/TriggerEventCard.tsx +45 -0
  37. package/skills/yad-docs/templates/app/src/components/DocLayout/DocPageShell.tsx +80 -0
  38. package/skills/yad-docs/templates/app/src/components/DocLayout/DocSectionCard.tsx +55 -0
  39. package/skills/yad-docs/templates/app/src/components/DocLayout/DocTableOfContents.tsx +79 -0
  40. package/skills/yad-docs/templates/app/src/components/DocLayout/RoleCard.tsx +67 -0
  41. package/skills/yad-docs/templates/app/src/components/DocSections/ApiReferenceSection.tsx +108 -0
  42. package/skills/yad-docs/templates/app/src/components/DocSections/CancelabilitySection.tsx +73 -0
  43. package/skills/yad-docs/templates/app/src/components/DocSections/CriticalRunbookSection.tsx +177 -0
  44. package/skills/yad-docs/templates/app/src/components/DocSections/DataMigrationSection.tsx +102 -0
  45. package/skills/yad-docs/templates/app/src/components/DocSections/DbSchemaSection.tsx +98 -0
  46. package/skills/yad-docs/templates/app/src/components/DocSections/DeploymentGuideSection.tsx +104 -0
  47. package/skills/yad-docs/templates/app/src/components/DocSections/DriverIntegrationSection.tsx +127 -0
  48. package/skills/yad-docs/templates/app/src/components/DocSections/ExecutiveSummarySection.tsx +69 -0
  49. package/skills/yad-docs/templates/app/src/components/DocSections/FlowOverviewSection.tsx +73 -0
  50. package/skills/yad-docs/templates/app/src/components/DocSections/FlowPathsChecklistSection.tsx +96 -0
  51. package/skills/yad-docs/templates/app/src/components/DocSections/MiddlewareChainSection.tsx +107 -0
  52. package/skills/yad-docs/templates/app/src/components/DocSections/MonitoringAlertingSection.tsx +106 -0
  53. package/skills/yad-docs/templates/app/src/components/DocSections/NotificationLocalizationSection.tsx +102 -0
  54. package/skills/yad-docs/templates/app/src/components/DocSections/PMRoadmapSection.tsx +133 -0
  55. package/skills/yad-docs/templates/app/src/components/DocSections/PerformanceTestingSection.tsx +91 -0
  56. package/skills/yad-docs/templates/app/src/components/DocSections/RiderIntegrationSection.tsx +99 -0
  57. package/skills/yad-docs/templates/app/src/components/DocSections/SecuritySection.tsx +74 -0
  58. package/skills/yad-docs/templates/app/src/components/DocSections/StatusMachineSection.tsx +90 -0
  59. package/skills/yad-docs/templates/app/src/components/DocSections/TestPlanSection.tsx +163 -0
  60. package/skills/yad-docs/templates/app/src/components/Logs/SystemLogsTerminal.tsx +126 -0
  61. package/skills/yad-docs/templates/app/src/components/Navigation/TopNavBar.tsx +90 -0
  62. package/skills/yad-docs/templates/app/src/components/Reference/BullMQJobsList.tsx +60 -0
  63. package/skills/yad-docs/templates/app/src/components/Reference/DecisionTreeView.tsx +49 -0
  64. package/skills/yad-docs/templates/app/src/components/Reference/DeeplinkActionsChips.tsx +69 -0
  65. package/skills/yad-docs/templates/app/src/components/Reference/DriverUIStatesTable.tsx +61 -0
  66. package/skills/yad-docs/templates/app/src/components/Reference/FeatureFlagMatrix.tsx +73 -0
  67. package/skills/yad-docs/templates/app/src/components/Reference/RiderUIStatesTable.tsx +61 -0
  68. package/skills/yad-docs/templates/app/src/components/Reference/RulesLegendPanel.tsx +217 -0
  69. package/skills/yad-docs/templates/app/src/components/Reference/StakeholderToggle.tsx +41 -0
  70. package/skills/yad-docs/templates/app/src/components/Reference/TroubleshootingSection.tsx +93 -0
  71. package/skills/yad-docs/templates/app/src/components/Sidebar/PathSelector.tsx +148 -0
  72. package/skills/yad-docs/templates/app/src/components/Sidebar/SidebarFooter.tsx +40 -0
  73. package/skills/yad-docs/templates/app/src/components/Sidebar/StepList.tsx +234 -0
  74. package/skills/yad-docs/templates/app/src/components/shared/Badge.tsx +28 -0
  75. package/skills/yad-docs/templates/app/src/components/shared/CommandPalette.tsx +213 -0
  76. package/skills/yad-docs/templates/app/src/components/shared/Icon.tsx +21 -0
  77. package/skills/yad-docs/templates/app/src/components/shared/Tooltip.tsx +42 -0
  78. package/skills/yad-docs/templates/app/src/data/components.ts +74 -0
  79. package/skills/yad-docs/templates/app/src/data/docSections.ts +231 -0
  80. package/skills/yad-docs/templates/app/src/data/paths.ts +2319 -0
  81. package/skills/yad-docs/templates/app/src/data/referenceData.ts +392 -0
  82. package/skills/yad-docs/templates/app/src/data/roles.ts +145 -0
  83. package/skills/yad-docs/templates/app/src/data/types.ts +79 -0
  84. package/skills/yad-docs/templates/app/src/hooks/useAnimationQueue.ts +41 -0
  85. package/skills/yad-docs/templates/app/src/hooks/usePlayback.ts +100 -0
  86. package/skills/yad-docs/templates/app/src/hooks/useStakeholderFilter.ts +10 -0
  87. package/skills/yad-docs/templates/app/src/index.css +121 -0
  88. package/skills/yad-docs/templates/app/src/main.tsx +13 -0
  89. package/skills/yad-docs/templates/app/src/pages/RoleSelectPage.tsx +34 -0
  90. package/skills/yad-docs/templates/app/src/pages/StakeholderDocPage.tsx +98 -0
  91. package/skills/yad-docs/templates/app/src/pages/SubPathDetailPage.tsx +282 -0
  92. package/skills/yad-docs/templates/app/src/store/useAuthStore.ts +42 -0
  93. package/skills/yad-docs/templates/app/src/store/useFlowStore.ts +197 -0
  94. package/skills/yad-docs/templates/app/src/utils/iconMap.ts +46 -0
  95. package/skills/yad-docs/templates/app/tsconfig.app.json +28 -0
  96. package/skills/yad-docs/templates/app/tsconfig.json +7 -0
  97. package/skills/yad-docs/templates/app/tsconfig.node.json +26 -0
  98. package/skills/yad-docs/templates/app/vite.config.ts +10 -0
  99. package/skills/yad-docs-overview/SKILL.md +129 -0
  100. package/skills/yad-docs-overview/references/pipeline-model.md +102 -0
  101. package/skills/yad-docs-sync/SKILL.md +99 -0
  102. package/skills/yad-docs-sync/references/staleness.md +81 -0
@@ -0,0 +1,216 @@
1
+ import React, { useRef, useMemo, useCallback, useState, useEffect } from 'react';
2
+ import { COMPONENTS } from '../../data/components';
3
+ import { useFlowStore } from '../../store/useFlowStore';
4
+ import { useAnimationQueue } from '../../hooks/useAnimationQueue';
5
+ import { SystemComponent } from './SystemComponent';
6
+ import { ConnectionLine } from './ConnectionLine';
7
+ import { AnimatedMessage } from './AnimatedMessage';
8
+ import { Icon } from '../shared/Icon';
9
+
10
+ const ALL_CONNECTIONS = [
11
+ ['rider-app', 'backend-api'],
12
+ ['driver-app', 'backend-api'],
13
+ ['backend-api', 'bullmq'],
14
+ ['backend-api', 'database'],
15
+ ['backend-api', 'pubsub'],
16
+ ['backend-api', 'dac'],
17
+ ['backend-api', 'ops-dashboard'],
18
+ ['ops-dashboard', 'backend-api'],
19
+ ];
20
+
21
+ export const FlowCanvas: React.FC = () => {
22
+ const containerRef = useRef<HTMLDivElement>(null);
23
+ const [dims, setDims] = useState({ width: 0, height: 0 });
24
+ const getCurrentStep = useFlowStore((s) => s.getCurrentStep);
25
+ const speed = useFlowStore((s) => s.speed);
26
+ const zoomLevel = useFlowStore((s) => s.zoomLevel);
27
+ const zoomIn = useFlowStore((s) => s.zoomIn);
28
+ const zoomOut = useFlowStore((s) => s.zoomOut);
29
+ const resetZoom = useFlowStore((s) => s.resetZoom);
30
+ const selectedPath = useFlowStore((s) => s.selectedPath);
31
+ const currentStep = getCurrentStep();
32
+ const { completedTargets, animKey } = useAnimationQueue();
33
+
34
+ useEffect(() => {
35
+ const el = containerRef.current;
36
+ if (!el) return;
37
+
38
+ const observer = new ResizeObserver((entries) => {
39
+ const entry = entries[0];
40
+ if (entry) {
41
+ setDims({
42
+ width: entry.contentRect.width,
43
+ height: entry.contentRect.height,
44
+ });
45
+ }
46
+ });
47
+ observer.observe(el);
48
+ return () => observer.disconnect();
49
+ }, []);
50
+
51
+ const activeComponents = useMemo(
52
+ () => new Set(currentStep?.activeComponents || []),
53
+ [currentStep]
54
+ );
55
+
56
+ const activeConnections = useMemo(() => {
57
+ if (!currentStep) return new Set<string>();
58
+ const conns = new Set<string>();
59
+ for (const msg of currentStep.messages) {
60
+ conns.add(`${msg.from}-${msg.to}`);
61
+ conns.add(`${msg.to}-${msg.from}`);
62
+ }
63
+ return conns;
64
+ }, [currentStep]);
65
+
66
+ const isConnectionActive = useCallback(
67
+ (from: string, to: string) => {
68
+ return (
69
+ activeConnections.has(`${from}-${to}`) ||
70
+ activeConnections.has(`${to}-${from}`)
71
+ );
72
+ },
73
+ [activeConnections]
74
+ );
75
+
76
+ const messages = currentStep?.messages || [];
77
+
78
+ return (
79
+ <div ref={containerRef} className="relative h-full w-full overflow-hidden">
80
+ {/* Canvas Header Overlay */}
81
+ <div className="absolute top-4 left-4 right-4 z-30 flex justify-between items-start pointer-events-none">
82
+ <div className="pointer-events-auto rounded-lg p-3 border shadow-xl"
83
+ style={{
84
+ background: 'rgba(30,26,37,0.8)',
85
+ backdropFilter: 'blur(12px)',
86
+ borderColor: 'rgba(255,255,255,0.05)',
87
+ }}
88
+ >
89
+ <h1 className="text-white text-xl font-bold font-display">
90
+ {selectedPath.label}
91
+ </h1>
92
+ <div className="flex items-center gap-2 mt-1">
93
+ <span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
94
+ <span className="text-xs text-slate-300 uppercase tracking-wide">Live Simulation</span>
95
+ </div>
96
+ </div>
97
+ <div className="pointer-events-auto flex gap-2">
98
+ <button
99
+ onClick={zoomIn}
100
+ className="h-10 w-10 flex items-center justify-center rounded-lg text-white border transition-colors"
101
+ style={{
102
+ background: 'rgba(30,26,37,0.8)',
103
+ borderColor: 'rgba(255,255,255,0.05)',
104
+ }}
105
+ >
106
+ <Icon name="zoom_in" size={20} />
107
+ </button>
108
+ <button
109
+ onClick={zoomOut}
110
+ className="h-10 w-10 flex items-center justify-center rounded-lg text-white border transition-colors"
111
+ style={{
112
+ background: 'rgba(30,26,37,0.8)',
113
+ borderColor: 'rgba(255,255,255,0.05)',
114
+ }}
115
+ >
116
+ <Icon name="zoom_out" size={20} />
117
+ </button>
118
+ <button
119
+ onClick={resetZoom}
120
+ className="h-10 w-10 flex items-center justify-center rounded-lg text-white border transition-colors"
121
+ style={{
122
+ background: 'rgba(30,26,37,0.8)',
123
+ borderColor: 'rgba(255,255,255,0.05)',
124
+ }}
125
+ >
126
+ <Icon name="center_focus_strong" size={20} />
127
+ </button>
128
+ </div>
129
+ </div>
130
+
131
+ {/* Zoomable content */}
132
+ <div
133
+ className="absolute inset-0"
134
+ style={{
135
+ transform: `scale(${zoomLevel})`,
136
+ transformOrigin: 'center center',
137
+ transition: 'transform 0.2s ease',
138
+ }}
139
+ >
140
+ {/* SVG layer for connection lines */}
141
+ {dims.width > 0 && (
142
+ <svg className="pointer-events-none absolute inset-0 h-full w-full">
143
+ <defs>
144
+ <linearGradient id="gradientPath" x1="0%" y1="0%" x2="100%" y2="0%">
145
+ <stop offset="0%" stopColor="#6116da" stopOpacity={1} />
146
+ <stop offset="100%" stopColor="#ff6490" stopOpacity={1} />
147
+ </linearGradient>
148
+ <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
149
+ <polygon points="0 0, 10 3.5, 0 7" fill="#ff6490" />
150
+ </marker>
151
+ </defs>
152
+ {ALL_CONNECTIONS.map(([from, to]) => (
153
+ <ConnectionLine
154
+ key={`${from}-${to}`}
155
+ from={from}
156
+ to={to}
157
+ isActive={isConnectionActive(from, to)}
158
+ containerWidth={dims.width}
159
+ containerHeight={dims.height}
160
+ />
161
+ ))}
162
+ </svg>
163
+ )}
164
+
165
+ {/* Animated messages */}
166
+ {dims.width > 0 &&
167
+ messages.map((msg) => (
168
+ <AnimatedMessage
169
+ key={`${animKey}-${msg.id}`}
170
+ from={msg.from}
171
+ to={msg.to}
172
+ label={msg.label}
173
+ color={msg.color}
174
+ delay={msg.delay}
175
+ duration={msg.duration}
176
+ speed={speed}
177
+ containerWidth={dims.width}
178
+ containerHeight={dims.height}
179
+ />
180
+ ))}
181
+
182
+ {/* System component nodes */}
183
+ {COMPONENTS.map((comp) => (
184
+ <SystemComponent
185
+ key={comp.id}
186
+ component={comp}
187
+ isActive={activeComponents.has(comp.id)}
188
+ isReceiving={completedTargets.has(comp.id)}
189
+ />
190
+ ))}
191
+ </div>
192
+
193
+ {/* Current status badge */}
194
+ {currentStep && (
195
+ <div
196
+ className="absolute left-4 bottom-4 z-30 rounded-lg border px-3 py-2"
197
+ style={{
198
+ background: 'rgba(30,26,37,0.85)',
199
+ backdropFilter: 'blur(8px)',
200
+ borderColor: 'var(--color-border-default)',
201
+ }}
202
+ >
203
+ <div className="text-[10px] uppercase tracking-wider" style={{ color: 'var(--color-text-muted)' }}>
204
+ Status
205
+ </div>
206
+ <div className="text-sm font-bold" style={{ color: 'var(--color-accent)' }}>
207
+ {currentStep.status}
208
+ </div>
209
+ <div className="mt-0.5 text-[10px]" style={{ color: 'var(--color-text-secondary)' }}>
210
+ {currentStep.bookingStatus}
211
+ </div>
212
+ </div>
213
+ )}
214
+ </div>
215
+ );
216
+ };
@@ -0,0 +1,153 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import type { SystemComponent as SystemComponentType } from '../../data/types';
4
+ import { Icon } from '../shared/Icon';
5
+ import { COMPONENT_ICONS, COMPONENT_ROLES } from '../../utils/iconMap';
6
+
7
+ interface SystemComponentProps {
8
+ component: SystemComponentType;
9
+ isActive: boolean;
10
+ isReceiving: boolean;
11
+ onClick?: () => void;
12
+ }
13
+
14
+ export const SystemComponent: React.FC<SystemComponentProps> = React.memo(
15
+ ({ component, isActive, isReceiving, onClick }) => {
16
+ const [hovered, setHovered] = useState(false);
17
+ const iconName = COMPONENT_ICONS[component.id] || 'circle';
18
+ const role = COMPONENT_ROLES[component.id] || 'Service';
19
+
20
+ const glowStyle = useMemo(() => {
21
+ if (isReceiving) {
22
+ return {
23
+ boxShadow: `0 0 30px ${component.color}40, 0 0 60px ${component.color}20`,
24
+ borderColor: `${component.color}80`,
25
+ };
26
+ }
27
+ if (isActive) {
28
+ return {
29
+ boxShadow: `0 0 20px ${component.color}30, 0 0 40px ${component.color}15`,
30
+ borderColor: `${component.color}50`,
31
+ };
32
+ }
33
+ return {
34
+ boxShadow: '0 4px 20px rgba(0,0,0,0.2)',
35
+ borderColor: 'rgba(255,255,255,0.05)',
36
+ };
37
+ }, [isActive, isReceiving, component.color]);
38
+
39
+ return (
40
+ <>
41
+ <motion.button
42
+ onClick={onClick}
43
+ onMouseEnter={() => setHovered(true)}
44
+ onMouseLeave={() => setHovered(false)}
45
+ className="absolute z-10 flex cursor-pointer flex-col items-center gap-2 rounded-2xl p-4 transition-colors glass-panel group"
46
+ style={{
47
+ left: `${component.position.x}%`,
48
+ top: `${component.position.y}%`,
49
+ transform: 'translate(-50%, -50%)',
50
+ background: isActive
51
+ ? `linear-gradient(135deg, ${component.color}12, ${component.color}06)`
52
+ : 'rgba(47, 41, 56, 0.4)',
53
+ backdropFilter: 'blur(12px)',
54
+ ...glowStyle,
55
+ minWidth: '130px',
56
+ minHeight: '140px',
57
+ }}
58
+ animate={
59
+ isReceiving
60
+ ? { scale: [1, 1.05, 1] }
61
+ : { scale: 1 }
62
+ }
63
+ transition={
64
+ isReceiving
65
+ ? { duration: 0.6, ease: 'easeInOut' }
66
+ : { duration: 0.3 }
67
+ }
68
+ whileHover={{ scale: 1.05 }}
69
+ aria-label={`${component.label}: ${component.description}`}
70
+ tabIndex={0}
71
+ >
72
+ {/* Role tag */}
73
+ <div className="absolute -top-3 px-3 py-1 rounded-full text-[10px] uppercase tracking-wider font-bold"
74
+ style={{
75
+ background: 'var(--color-surface-dark)',
76
+ border: '1px solid var(--color-surface-highlight)',
77
+ color: 'var(--color-text-muted)',
78
+ }}
79
+ >
80
+ {role}
81
+ </div>
82
+
83
+ {/* Icon with gradient background */}
84
+ <div className="h-14 w-14 rounded-2xl flex items-center justify-center text-white shadow-lg mb-1"
85
+ style={{
86
+ background: `linear-gradient(135deg, ${component.color}, ${component.color}99)`,
87
+ }}
88
+ >
89
+ <Icon name={iconName} size={28} className="text-white" />
90
+ </div>
91
+
92
+ {/* Label */}
93
+ <h3 className="text-white font-bold font-display text-sm text-center break-words w-full">{component.label}</h3>
94
+
95
+ {/* Status */}
96
+ <div className="flex items-center gap-1.5">
97
+ <motion.div
98
+ className="h-2 w-2 rounded-full"
99
+ style={{
100
+ backgroundColor: isActive ? '#22c55e' : isReceiving ? component.color : 'var(--color-text-muted)',
101
+ }}
102
+ animate={
103
+ isActive || isReceiving
104
+ ? { opacity: [0.7, 1, 0.7] }
105
+ : {}
106
+ }
107
+ transition={{ duration: 1.5, repeat: Infinity }}
108
+ />
109
+ <span className="text-xs text-slate-300">
110
+ {isReceiving ? 'Processing' : isActive ? 'Online' : 'Idle'}
111
+ </span>
112
+ </div>
113
+
114
+ {/* Connection points */}
115
+ <div className="absolute right-[-6px] top-1/2 -translate-y-1/2 h-3 w-3 rounded-full border-2 border-white"
116
+ style={{ background: component.color, opacity: isActive ? 1 : 0.3 }}
117
+ />
118
+ <div className="absolute left-[-6px] top-1/2 -translate-y-1/2 h-3 w-3 rounded-full border-2 border-white"
119
+ style={{ background: component.color, opacity: isActive ? 1 : 0.3 }}
120
+ />
121
+ </motion.button>
122
+
123
+ {/* Tooltip */}
124
+ <AnimatePresence>
125
+ {hovered && (
126
+ <motion.div
127
+ initial={{ opacity: 0, y: 4 }}
128
+ animate={{ opacity: 1, y: 0 }}
129
+ exit={{ opacity: 0, y: 4 }}
130
+ transition={{ duration: 0.15 }}
131
+ className="pointer-events-none absolute z-50 max-w-[220px] rounded-lg px-3 py-2 text-xs"
132
+ style={{
133
+ left: `${component.position.x}%`,
134
+ top: `${component.position.y}%`,
135
+ transform: 'translate(-50%, calc(-100% - 50px))',
136
+ backgroundColor: 'var(--color-surface-dark)',
137
+ color: 'var(--color-text-primary)',
138
+ border: '1px solid var(--color-border-light)',
139
+ boxShadow: '0 4px 20px rgba(0,0,0,0.4)',
140
+ backdropFilter: 'blur(8px)',
141
+ }}
142
+ >
143
+ <div className="font-semibold font-display">{component.label}</div>
144
+ <div className="mt-0.5 text-[10px]" style={{ color: 'var(--color-text-secondary)' }}>
145
+ {component.description}
146
+ </div>
147
+ </motion.div>
148
+ )}
149
+ </AnimatePresence>
150
+ </>
151
+ );
152
+ }
153
+ );
@@ -0,0 +1,284 @@
1
+ import { motion } from 'framer-motion';
2
+ import { useFlowStore } from '../../store/useFlowStore';
3
+ import { Icon } from '../shared/Icon';
4
+
5
+ function formatTime(seconds: number): string {
6
+ const mins = Math.floor(seconds / 60);
7
+ const secs = Math.floor(seconds % 60);
8
+ const cs = Math.floor((seconds % 1) * 100);
9
+ return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}.${String(cs).padStart(2, '0')}`;
10
+ }
11
+
12
+ export const PlaybackBar = () => {
13
+ const {
14
+ playbackState,
15
+ speed,
16
+ activeStepIndex,
17
+ elapsedTime,
18
+ play,
19
+ pause,
20
+ stop,
21
+ nextStep,
22
+ prevStep,
23
+ setSpeed,
24
+ setActiveStep,
25
+ getTotalSteps,
26
+ getCurrentStep,
27
+ getCurrentSteps,
28
+ toggleLogsPanel,
29
+ replayStep,
30
+ addLog,
31
+ } = useFlowStore();
32
+
33
+ const totalSteps = getTotalSteps();
34
+ const currentStep = getCurrentStep();
35
+ const steps = getCurrentSteps();
36
+ const progress = totalSteps > 0 ? ((activeStepIndex + 1) / totalSteps) * 100 : 0;
37
+ const estimatedTotal = totalSteps * 8;
38
+
39
+ const handleFlagIssue = () => {
40
+ if (currentStep) {
41
+ addLog({
42
+ level: 'warn',
43
+ source: 'USER',
44
+ message: `Flagged step ${activeStepIndex + 1}: ${currentStep.title}`,
45
+ });
46
+ }
47
+ };
48
+
49
+ return (
50
+ <div className="relative z-20">
51
+ {/* Ambient glow */}
52
+ <div
53
+ className="absolute inset-0 pointer-events-none overflow-hidden"
54
+ style={{ background: 'rgba(30,26,37,0.9)' }}
55
+ >
56
+ <div
57
+ className="absolute left-1/2 -translate-x-1/2 top-0 w-1/3 h-full"
58
+ style={{
59
+ background: 'rgba(97,22,218,0.05)',
60
+ filter: 'blur(100px)',
61
+ }}
62
+ />
63
+ </div>
64
+
65
+ <div
66
+ className="relative"
67
+ style={{
68
+ borderTop: '1px solid rgba(255,255,255,0.1)',
69
+ }}
70
+ >
71
+ {/* Header: label + timer */}
72
+ <div className="flex items-center justify-between px-6 pt-3 pb-1">
73
+ <span
74
+ className="text-[10px] font-semibold uppercase tracking-widest"
75
+ style={{ color: 'var(--color-text-muted)' }}
76
+ >
77
+ Booking Flow Progress
78
+ </span>
79
+ <div className="font-mono text-xs" style={{ color: 'var(--color-text-muted)' }}>
80
+ <span style={{ color: 'var(--color-primary)' }}>{formatTime(elapsedTime)}</span>
81
+ {' / '}
82
+ <span>{formatTime(estimatedTotal)}</span>
83
+ </div>
84
+ </div>
85
+
86
+ {/* Step title */}
87
+ <div className="px-6 pb-2">
88
+ <h3 className="text-white text-base font-bold">
89
+ {currentStep
90
+ ? `Step ${activeStepIndex + 1}: ${currentStep.title}`
91
+ : 'Select a path to begin'}
92
+ </h3>
93
+ </div>
94
+
95
+ {/* Step markers + progress bar */}
96
+ <div className="px-6 pb-3">
97
+ {/* Markers row */}
98
+ <div className="relative flex items-start justify-between mb-1">
99
+ {steps.map((step, i) => {
100
+ const isCompleted = i < activeStepIndex;
101
+ const isActive = i === activeStepIndex;
102
+ const shortLabel = step.title.split(' ')[0];
103
+
104
+ return (
105
+ <button
106
+ key={step.id}
107
+ onClick={() => setActiveStep(i)}
108
+ className="flex flex-col items-center gap-1 z-10 group cursor-pointer"
109
+ style={{ flex: '1 1 0' }}
110
+ title={step.title}
111
+ >
112
+ <div
113
+ className="rounded-full transition-all duration-300"
114
+ style={{
115
+ width: isActive ? 14 : 10,
116
+ height: isActive ? 14 : 10,
117
+ background: isCompleted
118
+ ? 'var(--color-primary)'
119
+ : isActive
120
+ ? 'white'
121
+ : 'rgba(255,255,255,0.15)',
122
+ border: isActive ? '3px solid var(--color-primary)' : 'none',
123
+ boxShadow: isActive
124
+ ? '0 0 12px rgba(97,22,218,0.6)'
125
+ : isCompleted
126
+ ? '0 0 6px rgba(97,22,218,0.3)'
127
+ : 'none',
128
+ }}
129
+ />
130
+ <span
131
+ className="text-[9px] font-medium transition-colors"
132
+ style={{
133
+ color: isActive
134
+ ? 'white'
135
+ : isCompleted
136
+ ? 'var(--color-primary)'
137
+ : 'var(--color-text-muted)',
138
+ }}
139
+ >
140
+ {shortLabel}
141
+ </span>
142
+ </button>
143
+ );
144
+ })}
145
+ </div>
146
+
147
+ {/* Progress bar */}
148
+ <div
149
+ className="relative h-1.5 w-full rounded-full overflow-hidden"
150
+ style={{ background: 'rgba(255,255,255,0.08)' }}
151
+ >
152
+ <motion.div
153
+ className="h-full rounded-full"
154
+ style={{
155
+ background: 'linear-gradient(90deg, var(--color-primary), #a855f7)',
156
+ }}
157
+ animate={{ width: `${progress}%` }}
158
+ transition={{ duration: 0.3 }}
159
+ />
160
+ </div>
161
+ </div>
162
+
163
+ {/* Controls row */}
164
+ <div className="flex items-center gap-3 px-6 pb-3">
165
+ {/* Skip Prev */}
166
+ <button
167
+ onClick={prevStep}
168
+ disabled={activeStepIndex === 0}
169
+ className="group flex flex-col items-center gap-0.5 p-1.5 rounded-md transition-colors disabled:opacity-30"
170
+ style={{ color: 'var(--color-text-secondary)' }}
171
+ >
172
+ <Icon name="skip_previous" size={22} />
173
+ <span className="text-[8px] opacity-0 group-hover:opacity-100 transition-opacity">Prev</span>
174
+ </button>
175
+
176
+ {/* Play / Pause */}
177
+ <motion.button
178
+ onClick={playbackState === 'playing' ? pause : play}
179
+ className="h-14 w-14 rounded-full flex items-center justify-center text-white transition-transform active:scale-95"
180
+ style={{
181
+ background: 'var(--color-primary)',
182
+ boxShadow: '0 0 20px rgba(97,22,218,0.5)',
183
+ }}
184
+ whileHover={{ scale: 1.05, boxShadow: '0 0 30px rgba(97,22,218,0.8)' }}
185
+ whileTap={{ scale: 0.95 }}
186
+ aria-label={playbackState === 'playing' ? 'Pause' : 'Play'}
187
+ >
188
+ <Icon name={playbackState === 'playing' ? 'pause' : 'play_arrow'} size={32} filled />
189
+ </motion.button>
190
+
191
+ {/* Skip Next */}
192
+ <button
193
+ onClick={nextStep}
194
+ disabled={activeStepIndex >= totalSteps - 1}
195
+ className="group flex flex-col items-center gap-0.5 p-1.5 rounded-md transition-colors disabled:opacity-30"
196
+ style={{ color: 'var(--color-text-secondary)' }}
197
+ >
198
+ <Icon name="skip_next" size={22} />
199
+ <span className="text-[8px] opacity-0 group-hover:opacity-100 transition-opacity">Next</span>
200
+ </button>
201
+
202
+ {/* Stop */}
203
+ <button
204
+ onClick={stop}
205
+ className="p-1.5 rounded-md transition-colors"
206
+ style={{ color: 'var(--color-text-secondary)' }}
207
+ aria-label="Stop"
208
+ >
209
+ <Icon name="stop" size={22} />
210
+ </button>
211
+
212
+ {/* Divider */}
213
+ <div className="h-8 w-px" style={{ background: 'rgba(255,255,255,0.1)' }} />
214
+
215
+ {/* Speed control */}
216
+ <div className="flex rounded-lg p-0.5" style={{ background: 'rgba(255,255,255,0.05)' }}>
217
+ {[1, 2, 3].map((s) => (
218
+ <button
219
+ key={s}
220
+ onClick={() => setSpeed(s)}
221
+ className="px-3 py-1 rounded-md text-xs font-bold transition-all"
222
+ style={{
223
+ background: speed === s ? 'rgba(97,22,218,0.2)' : 'transparent',
224
+ color: speed === s ? 'white' : 'var(--color-text-muted)',
225
+ border: speed === s ? '1px solid rgba(97,22,218,0.3)' : '1px solid transparent',
226
+ }}
227
+ >
228
+ {s}x
229
+ </button>
230
+ ))}
231
+ </div>
232
+
233
+ {/* Divider */}
234
+ <div className="h-8 w-px" style={{ background: 'rgba(255,255,255,0.1)' }} />
235
+
236
+ {/* Replay Step */}
237
+ <button
238
+ onClick={replayStep}
239
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-medium transition-colors"
240
+ style={{
241
+ background: 'var(--color-surface-dark)',
242
+ borderColor: 'var(--color-border-default)',
243
+ color: 'var(--color-text-secondary)',
244
+ }}
245
+ >
246
+ <Icon name="history" size={16} />
247
+ Replay Step
248
+ </button>
249
+
250
+ {/* Flag Issue */}
251
+ <button
252
+ onClick={handleFlagIssue}
253
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-medium transition-colors"
254
+ style={{
255
+ background: 'var(--color-surface-dark)',
256
+ borderColor: 'var(--color-border-default)',
257
+ color: 'var(--color-text-secondary)',
258
+ }}
259
+ >
260
+ <Icon name="bug_report" size={16} />
261
+ Flag Issue
262
+ </button>
263
+
264
+ {/* Divider */}
265
+ <div className="h-8 w-px" style={{ background: 'rgba(255,255,255,0.1)' }} />
266
+
267
+ {/* Logs toggle */}
268
+ <button
269
+ onClick={toggleLogsPanel}
270
+ className="flex items-center gap-2 px-3 py-1.5 rounded-lg border text-xs font-medium transition-colors"
271
+ style={{
272
+ background: 'var(--color-surface-dark)',
273
+ borderColor: 'var(--color-border-default)',
274
+ color: 'var(--color-text-secondary)',
275
+ }}
276
+ >
277
+ <Icon name="terminal" size={16} />
278
+ Logs
279
+ </button>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ );
284
+ };