yadflow 2.6.0 → 2.8.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.
- package/CHANGELOG.md +2 -11
- package/README.md +30 -5
- package/bin/yad.mjs +36 -1
- package/cli/docs.mjs +298 -0
- package/cli/manifest.mjs +6 -1
- package/cli/roster.mjs +164 -0
- package/cli/setup.mjs +128 -2
- package/package.json +3 -4
- package/skills/sdlc/config.yaml +19 -0
- package/skills/sdlc/install.sh +1 -1
- package/skills/sdlc/module-help.csv +4 -0
- package/skills/yad-connect-docs/SKILL.md +132 -0
- package/skills/yad-connect-docs/references/docs-registry.md +74 -0
- package/skills/yad-connect-repos/SKILL.md +4 -0
- package/skills/yad-connect-repos/references/hub-config.md +3 -1
- package/skills/yad-docs/SKILL.md +159 -0
- package/skills/yad-docs/references/data-mapping.md +75 -0
- package/skills/yad-docs/references/theme-map.md +69 -0
- package/skills/yad-docs/templates/app/README.md +31 -0
- package/skills/yad-docs/templates/app/eslint.config.js +23 -0
- package/skills/yad-docs/templates/app/index.html +17 -0
- package/skills/yad-docs/templates/app/package-lock.json +4030 -0
- package/skills/yad-docs/templates/app/package.json +35 -0
- package/skills/yad-docs/templates/app/public/favicon.svg +28 -0
- package/skills/yad-docs/templates/app/public/logo.svg +39 -0
- package/skills/yad-docs/templates/app/public/vite.svg +1 -0
- package/skills/yad-docs/templates/app/src/App.tsx +98 -0
- package/skills/yad-docs/templates/app/src/components/Auth/LoginPage.tsx +101 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/AnimatedMessage.tsx +101 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/ConnectionLine.tsx +90 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +216 -0
- package/skills/yad-docs/templates/app/src/components/Canvas/SystemComponent.tsx +153 -0
- package/skills/yad-docs/templates/app/src/components/Controls/PlaybackBar.tsx +284 -0
- package/skills/yad-docs/templates/app/src/components/Controls/StepDetail.tsx +167 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/HandlerLogicSnippet.tsx +41 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/RequestPayloadPreview.tsx +46 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/RightPanel.tsx +88 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/StatusCard.tsx +76 -0
- package/skills/yad-docs/templates/app/src/components/DetailPanel/TriggerEventCard.tsx +45 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/DocPageShell.tsx +80 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/DocSectionCard.tsx +55 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/DocTableOfContents.tsx +79 -0
- package/skills/yad-docs/templates/app/src/components/DocLayout/RoleCard.tsx +67 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/ApiReferenceSection.tsx +108 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/CancelabilitySection.tsx +73 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/CriticalRunbookSection.tsx +177 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DataMigrationSection.tsx +102 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DbSchemaSection.tsx +98 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DeploymentGuideSection.tsx +104 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/DriverIntegrationSection.tsx +127 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/ExecutiveSummarySection.tsx +69 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/FlowOverviewSection.tsx +73 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/FlowPathsChecklistSection.tsx +96 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/MiddlewareChainSection.tsx +107 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/MonitoringAlertingSection.tsx +106 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/NotificationLocalizationSection.tsx +102 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/PMRoadmapSection.tsx +133 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/PerformanceTestingSection.tsx +91 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/RiderIntegrationSection.tsx +99 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/SecuritySection.tsx +74 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/StatusMachineSection.tsx +90 -0
- package/skills/yad-docs/templates/app/src/components/DocSections/TestPlanSection.tsx +163 -0
- package/skills/yad-docs/templates/app/src/components/Logs/SystemLogsTerminal.tsx +126 -0
- package/skills/yad-docs/templates/app/src/components/Navigation/TopNavBar.tsx +90 -0
- package/skills/yad-docs/templates/app/src/components/Reference/BullMQJobsList.tsx +60 -0
- package/skills/yad-docs/templates/app/src/components/Reference/DecisionTreeView.tsx +49 -0
- package/skills/yad-docs/templates/app/src/components/Reference/DeeplinkActionsChips.tsx +69 -0
- package/skills/yad-docs/templates/app/src/components/Reference/DriverUIStatesTable.tsx +61 -0
- package/skills/yad-docs/templates/app/src/components/Reference/FeatureFlagMatrix.tsx +73 -0
- package/skills/yad-docs/templates/app/src/components/Reference/RiderUIStatesTable.tsx +61 -0
- package/skills/yad-docs/templates/app/src/components/Reference/RulesLegendPanel.tsx +217 -0
- package/skills/yad-docs/templates/app/src/components/Reference/StakeholderToggle.tsx +41 -0
- package/skills/yad-docs/templates/app/src/components/Reference/TroubleshootingSection.tsx +93 -0
- package/skills/yad-docs/templates/app/src/components/Sidebar/PathSelector.tsx +148 -0
- package/skills/yad-docs/templates/app/src/components/Sidebar/SidebarFooter.tsx +40 -0
- package/skills/yad-docs/templates/app/src/components/Sidebar/StepList.tsx +234 -0
- package/skills/yad-docs/templates/app/src/components/shared/Badge.tsx +28 -0
- package/skills/yad-docs/templates/app/src/components/shared/CommandPalette.tsx +213 -0
- package/skills/yad-docs/templates/app/src/components/shared/Icon.tsx +21 -0
- package/skills/yad-docs/templates/app/src/components/shared/Tooltip.tsx +42 -0
- package/skills/yad-docs/templates/app/src/data/components.ts +74 -0
- package/skills/yad-docs/templates/app/src/data/docSections.ts +231 -0
- package/skills/yad-docs/templates/app/src/data/paths.ts +2319 -0
- package/skills/yad-docs/templates/app/src/data/referenceData.ts +392 -0
- package/skills/yad-docs/templates/app/src/data/roles.ts +145 -0
- package/skills/yad-docs/templates/app/src/data/types.ts +79 -0
- package/skills/yad-docs/templates/app/src/hooks/useAnimationQueue.ts +41 -0
- package/skills/yad-docs/templates/app/src/hooks/usePlayback.ts +100 -0
- package/skills/yad-docs/templates/app/src/hooks/useStakeholderFilter.ts +10 -0
- package/skills/yad-docs/templates/app/src/index.css +121 -0
- package/skills/yad-docs/templates/app/src/main.tsx +13 -0
- package/skills/yad-docs/templates/app/src/pages/RoleSelectPage.tsx +34 -0
- package/skills/yad-docs/templates/app/src/pages/StakeholderDocPage.tsx +98 -0
- package/skills/yad-docs/templates/app/src/pages/SubPathDetailPage.tsx +282 -0
- package/skills/yad-docs/templates/app/src/store/useAuthStore.ts +42 -0
- package/skills/yad-docs/templates/app/src/store/useFlowStore.ts +197 -0
- package/skills/yad-docs/templates/app/src/utils/iconMap.ts +46 -0
- package/skills/yad-docs/templates/app/tsconfig.app.json +28 -0
- package/skills/yad-docs/templates/app/tsconfig.json +7 -0
- package/skills/yad-docs/templates/app/tsconfig.node.json +26 -0
- package/skills/yad-docs/templates/app/vite.config.ts +10 -0
- package/skills/yad-docs-overview/SKILL.md +131 -0
- package/skills/yad-docs-overview/references/pipeline-model.md +102 -0
- package/skills/yad-docs-sync/SKILL.md +99 -0
- package/skills/yad-docs-sync/references/staleness.md +81 -0
- package/skills/yad-hub-bridge/references/login-roster.md +1 -0
- package/docs/index.html +0 -1323
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
3
|
+
import { useFlowStore } from "../../store/useFlowStore";
|
|
4
|
+
import { Badge } from "../shared/Badge";
|
|
5
|
+
|
|
6
|
+
export const StepDetail: React.FC = () => {
|
|
7
|
+
const getCurrentStep = useFlowStore((s) => s.getCurrentStep);
|
|
8
|
+
const step = getCurrentStep();
|
|
9
|
+
|
|
10
|
+
if (!step) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className="flex h-full items-center justify-center text-sm"
|
|
14
|
+
style={{ color: "var(--color-text-muted)" }}
|
|
15
|
+
>
|
|
16
|
+
Select a path to view step details
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<AnimatePresence mode="wait">
|
|
23
|
+
<motion.div
|
|
24
|
+
key={step.id}
|
|
25
|
+
initial={{ opacity: 0, y: 8 }}
|
|
26
|
+
animate={{ opacity: 1, y: 0 }}
|
|
27
|
+
exit={{ opacity: 0, y: -8 }}
|
|
28
|
+
transition={{ duration: 0.2 }}
|
|
29
|
+
className="flex h-full flex-col gap-2 overflow-auto px-4 py-2"
|
|
30
|
+
>
|
|
31
|
+
{/* Header */}
|
|
32
|
+
<div className="flex items-start justify-between gap-2">
|
|
33
|
+
<div>
|
|
34
|
+
<div
|
|
35
|
+
className="text-xs font-bold"
|
|
36
|
+
style={{ color: "var(--color-text-primary)" }}
|
|
37
|
+
>
|
|
38
|
+
{step.title}
|
|
39
|
+
</div>
|
|
40
|
+
<div
|
|
41
|
+
className="mt-0.5 text-[11px] leading-relaxed"
|
|
42
|
+
style={{ color: "var(--color-text-secondary)" }}
|
|
43
|
+
>
|
|
44
|
+
{step.description}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Grid of details */}
|
|
50
|
+
<div className="grid grid-cols-2 gap-x-4 gap-y-1.5">
|
|
51
|
+
<DetailItem label="Trigger" value={step.trigger} />
|
|
52
|
+
<DetailItem label="Handler" value={step.handler} mono />
|
|
53
|
+
<DetailItem label="Status" value={step.status} highlight />
|
|
54
|
+
<DetailItem
|
|
55
|
+
label="Booking Status"
|
|
56
|
+
value={step.bookingStatus}
|
|
57
|
+
highlight
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{/* Side Effects */}
|
|
62
|
+
{Object.keys(step.sideEffects).length > 0 && (
|
|
63
|
+
<div className="flex flex-wrap gap-1.5">
|
|
64
|
+
{step.sideEffects.jobs && (
|
|
65
|
+
<SideEffectPill icon="⏱️" label="Jobs" value={step.sideEffects.jobs} />
|
|
66
|
+
)}
|
|
67
|
+
{step.sideEffects.notifications && (
|
|
68
|
+
<SideEffectPill
|
|
69
|
+
icon="🔔"
|
|
70
|
+
label="Notifications"
|
|
71
|
+
value={step.sideEffects.notifications}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
{step.sideEffects.dac && (
|
|
75
|
+
<SideEffectPill icon="📋" label="DAC" value={step.sideEffects.dac} />
|
|
76
|
+
)}
|
|
77
|
+
{step.sideEffects.pubsub && (
|
|
78
|
+
<SideEffectPill icon="📡" label="Pub/Sub" value={step.sideEffects.pubsub} />
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{/* Message types in this step */}
|
|
84
|
+
{step.messages.length > 0 && (
|
|
85
|
+
<div className="flex flex-wrap gap-1">
|
|
86
|
+
{Array.from(new Set(step.messages.map((m) => m.type))).map(
|
|
87
|
+
(type) => (
|
|
88
|
+
<Badge key={type} type={type} />
|
|
89
|
+
)
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</motion.div>
|
|
94
|
+
</AnimatePresence>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function DetailItem({
|
|
99
|
+
label,
|
|
100
|
+
value,
|
|
101
|
+
mono,
|
|
102
|
+
highlight,
|
|
103
|
+
}: {
|
|
104
|
+
label: string;
|
|
105
|
+
value: string;
|
|
106
|
+
mono?: boolean;
|
|
107
|
+
highlight?: boolean;
|
|
108
|
+
}) {
|
|
109
|
+
return (
|
|
110
|
+
<div className="min-w-0">
|
|
111
|
+
<div
|
|
112
|
+
className="text-[9px] uppercase tracking-wider"
|
|
113
|
+
style={{ color: "var(--color-text-muted)" }}
|
|
114
|
+
>
|
|
115
|
+
{label}
|
|
116
|
+
</div>
|
|
117
|
+
<div
|
|
118
|
+
className={`truncate text-[11px] ${mono ? "font-mono" : "font-medium"}`}
|
|
119
|
+
style={{
|
|
120
|
+
color: highlight
|
|
121
|
+
? "var(--color-accent)"
|
|
122
|
+
: "var(--color-text-secondary)",
|
|
123
|
+
}}
|
|
124
|
+
title={value}
|
|
125
|
+
>
|
|
126
|
+
{value}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function SideEffectPill({
|
|
133
|
+
icon,
|
|
134
|
+
label,
|
|
135
|
+
value,
|
|
136
|
+
}: {
|
|
137
|
+
icon: string;
|
|
138
|
+
label: string;
|
|
139
|
+
value: string;
|
|
140
|
+
}) {
|
|
141
|
+
return (
|
|
142
|
+
<div
|
|
143
|
+
className="flex items-start gap-1 rounded-md border px-2 py-1"
|
|
144
|
+
style={{
|
|
145
|
+
borderColor: "var(--color-border-default)",
|
|
146
|
+
background: "var(--color-bg-secondary)",
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<span className="text-[10px]">{icon}</span>
|
|
150
|
+
<div className="min-w-0">
|
|
151
|
+
<div
|
|
152
|
+
className="text-[9px] font-bold uppercase"
|
|
153
|
+
style={{ color: "var(--color-text-muted)" }}
|
|
154
|
+
>
|
|
155
|
+
{label}
|
|
156
|
+
</div>
|
|
157
|
+
<div
|
|
158
|
+
className="text-[10px] leading-snug"
|
|
159
|
+
style={{ color: "var(--color-text-secondary)" }}
|
|
160
|
+
title={value}
|
|
161
|
+
>
|
|
162
|
+
{value}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { FlowStep } from '../../data/types';
|
|
2
|
+
import { Icon } from '../shared/Icon';
|
|
3
|
+
|
|
4
|
+
interface HandlerLogicSnippetProps {
|
|
5
|
+
step: FlowStep;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function HandlerLogicSnippet({ step }: HandlerLogicSnippetProps) {
|
|
9
|
+
const handlerParts = step.handler.split('.');
|
|
10
|
+
const fileName = handlerParts.length > 1 ? handlerParts[0] + '.ts' : 'handler.ts';
|
|
11
|
+
const functionName = handlerParts.length > 1 ? handlerParts[1] : step.handler;
|
|
12
|
+
|
|
13
|
+
const code = `async ${functionName}(req) {
|
|
14
|
+
// trigger: ${step.trigger}
|
|
15
|
+
// status: ${step.status} → ${step.bookingStatus}
|
|
16
|
+
${step.messages.map((m) => `await this.emit('${m.type}', '${m.label}');`).join('\n ')}
|
|
17
|
+
}`;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<h4 className="text-xs font-bold text-slate-400 uppercase mb-2 flex items-center gap-2">
|
|
22
|
+
<Icon name="code" size={16} /> Handler Logic
|
|
23
|
+
</h4>
|
|
24
|
+
<div className="rounded-lg p-3 border"
|
|
25
|
+
style={{
|
|
26
|
+
background: 'var(--color-surface-darker)',
|
|
27
|
+
borderColor: 'var(--color-surface-highlight)',
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<div className="mb-2 pb-2"
|
|
31
|
+
style={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }}
|
|
32
|
+
>
|
|
33
|
+
<span className="text-[10px] text-slate-400">{functionName}() — {fileName}</span>
|
|
34
|
+
</div>
|
|
35
|
+
<pre className="text-[11px] text-blue-300 font-mono leading-relaxed overflow-x-auto">
|
|
36
|
+
<code>{code}</code>
|
|
37
|
+
</pre>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FlowStep } from '../../data/types';
|
|
2
|
+
import { Icon } from '../shared/Icon';
|
|
3
|
+
|
|
4
|
+
interface RequestPayloadPreviewProps {
|
|
5
|
+
step: FlowStep;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function RequestPayloadPreview({ step }: RequestPayloadPreviewProps) {
|
|
9
|
+
const payload = {
|
|
10
|
+
step_id: step.id,
|
|
11
|
+
actor: step.actor,
|
|
12
|
+
status: step.status,
|
|
13
|
+
booking_status: step.bookingStatus,
|
|
14
|
+
trigger: step.trigger,
|
|
15
|
+
active_components: step.activeComponents,
|
|
16
|
+
messages: step.messages.map((m) => ({
|
|
17
|
+
from: m.from,
|
|
18
|
+
to: m.to,
|
|
19
|
+
type: m.type,
|
|
20
|
+
label: m.label,
|
|
21
|
+
})),
|
|
22
|
+
...(Object.keys(step.sideEffects).length > 0 && {
|
|
23
|
+
side_effects: step.sideEffects,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const jsonStr = JSON.stringify(payload, null, 2);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<h4 className="text-xs font-bold text-slate-400 uppercase mb-2 flex items-center gap-2">
|
|
32
|
+
<Icon name="data_object" size={16} /> Request Payload
|
|
33
|
+
</h4>
|
|
34
|
+
<div className="rounded-lg p-3 border overflow-hidden"
|
|
35
|
+
style={{
|
|
36
|
+
background: 'var(--color-surface-darker)',
|
|
37
|
+
borderColor: 'var(--color-surface-highlight)',
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<pre className="text-[11px] text-green-400 font-mono leading-relaxed overflow-x-auto max-h-40">
|
|
41
|
+
<code>{jsonStr}</code>
|
|
42
|
+
</pre>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import { useFlowStore } from '../../store/useFlowStore';
|
|
4
|
+
import { StatusCard } from './StatusCard';
|
|
5
|
+
import { TriggerEventCard } from './TriggerEventCard';
|
|
6
|
+
import { RequestPayloadPreview } from './RequestPayloadPreview';
|
|
7
|
+
import { HandlerLogicSnippet } from './HandlerLogicSnippet';
|
|
8
|
+
import { Icon } from '../shared/Icon';
|
|
9
|
+
|
|
10
|
+
export function RightPanel() {
|
|
11
|
+
const getCurrentStep = useFlowStore((s) => s.getCurrentStep);
|
|
12
|
+
const activeStepIndex = useFlowStore((s) => s.activeStepIndex);
|
|
13
|
+
const selectedPath = useFlowStore((s) => s.selectedPath);
|
|
14
|
+
const navigate = useNavigate();
|
|
15
|
+
const step = getCurrentStep();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<aside
|
|
19
|
+
className="w-80 flex-none flex flex-col border-l z-10"
|
|
20
|
+
style={{
|
|
21
|
+
borderColor: 'var(--color-border-default)',
|
|
22
|
+
background: 'var(--color-bg-primary)',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<div className="p-5 border-b flex justify-between items-center"
|
|
26
|
+
style={{ borderColor: 'var(--color-border-default)' }}
|
|
27
|
+
>
|
|
28
|
+
<h3 className="text-slate-100 text-sm font-bold font-display uppercase tracking-wider">
|
|
29
|
+
Step Details
|
|
30
|
+
</h3>
|
|
31
|
+
<Icon name="close" size={20} className="text-slate-500 cursor-pointer hover:text-white" />
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
{!step ? (
|
|
35
|
+
<div className="flex-1 flex items-center justify-center p-5">
|
|
36
|
+
<p className="text-sm text-center" style={{ color: 'var(--color-text-muted)' }}>
|
|
37
|
+
Select a path to view step details
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
) : (
|
|
41
|
+
<AnimatePresence mode="wait">
|
|
42
|
+
<motion.div
|
|
43
|
+
key={step.id}
|
|
44
|
+
initial={{ opacity: 0, y: 8 }}
|
|
45
|
+
animate={{ opacity: 1, y: 0 }}
|
|
46
|
+
exit={{ opacity: 0, y: -8 }}
|
|
47
|
+
transition={{ duration: 0.2 }}
|
|
48
|
+
className="flex-1 overflow-y-auto p-5 space-y-6"
|
|
49
|
+
>
|
|
50
|
+
<StatusCard step={step} stepIndex={activeStepIndex} />
|
|
51
|
+
<TriggerEventCard step={step} />
|
|
52
|
+
<RequestPayloadPreview step={step} />
|
|
53
|
+
<HandlerLogicSnippet step={step} />
|
|
54
|
+
</motion.div>
|
|
55
|
+
</AnimatePresence>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{step && (
|
|
59
|
+
<div className="p-4 border-t flex flex-col gap-2" style={{ borderColor: 'var(--color-border-default)' }}>
|
|
60
|
+
<button
|
|
61
|
+
onClick={() => navigate(`/path/${selectedPath.id}`)}
|
|
62
|
+
className="w-full py-2.5 rounded-lg text-white text-sm font-medium transition-colors flex items-center justify-center gap-2"
|
|
63
|
+
style={{
|
|
64
|
+
background: 'var(--color-primary)',
|
|
65
|
+
boxShadow: '0 4px 15px rgba(97,22,218,0.2)',
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<Icon name="open_in_new" size={18} />
|
|
69
|
+
View Full Path Details
|
|
70
|
+
</button>
|
|
71
|
+
<button
|
|
72
|
+
onClick={() => console.log('Debug step:', step)}
|
|
73
|
+
className="w-full py-2.5 rounded-lg text-slate-300 text-sm font-medium border transition-colors flex items-center justify-center gap-2"
|
|
74
|
+
style={{
|
|
75
|
+
background: 'rgba(255,255,255,0.05)',
|
|
76
|
+
borderColor: 'rgba(255,255,255,0.05)',
|
|
77
|
+
}}
|
|
78
|
+
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.1)'}
|
|
79
|
+
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.05)'}
|
|
80
|
+
>
|
|
81
|
+
<Icon name="bug_report" size={18} />
|
|
82
|
+
Debug Step
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</aside>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { FlowStep } from '../../data/types';
|
|
2
|
+
import { Icon } from '../shared/Icon';
|
|
3
|
+
|
|
4
|
+
interface StatusCardProps {
|
|
5
|
+
step: FlowStep;
|
|
6
|
+
stepIndex: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function StatusCard({ step, stepIndex }: StatusCardProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="rounded-xl p-4 border"
|
|
12
|
+
style={{
|
|
13
|
+
background: 'var(--color-surface-highlight)',
|
|
14
|
+
borderColor: 'var(--color-surface-highlight)',
|
|
15
|
+
}}
|
|
16
|
+
>
|
|
17
|
+
<div className="flex items-center justify-between mb-3">
|
|
18
|
+
<span className="text-xs font-bold text-slate-400 uppercase">Current State</span>
|
|
19
|
+
<span className="text-[10px] font-mono px-2 py-0.5 rounded border"
|
|
20
|
+
style={{
|
|
21
|
+
background: 'rgba(59,130,246,0.2)',
|
|
22
|
+
color: 'rgb(147,197,253)',
|
|
23
|
+
borderColor: 'rgba(59,130,246,0.3)',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
Step {stepIndex + 1}
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="flex items-center gap-3 mb-2">
|
|
30
|
+
<div className="h-3 w-3 rounded-full animate-pulse"
|
|
31
|
+
style={{ backgroundColor: '#eab308' }}
|
|
32
|
+
/>
|
|
33
|
+
<h2 className="text-white text-lg font-bold font-display break-all">{step.bookingStatus}</h2>
|
|
34
|
+
</div>
|
|
35
|
+
<p className="text-slate-400 text-sm leading-relaxed">
|
|
36
|
+
{step.description}
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
{/* Side effects badges */}
|
|
40
|
+
{Object.keys(step.sideEffects).length > 0 && (
|
|
41
|
+
<div className="flex flex-wrap gap-1.5 mt-3 pt-3 border-t"
|
|
42
|
+
style={{ borderColor: 'rgba(255,255,255,0.05)' }}
|
|
43
|
+
>
|
|
44
|
+
{step.sideEffects.jobs && (
|
|
45
|
+
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md"
|
|
46
|
+
style={{ background: 'rgba(245,158,11,0.15)', color: '#f59e0b' }}
|
|
47
|
+
>
|
|
48
|
+
<Icon name="work" size={12} /> {step.sideEffects.jobs}
|
|
49
|
+
</span>
|
|
50
|
+
)}
|
|
51
|
+
{step.sideEffects.notifications && (
|
|
52
|
+
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md"
|
|
53
|
+
style={{ background: 'rgba(251,37,118,0.15)', color: '#fb2576' }}
|
|
54
|
+
>
|
|
55
|
+
<Icon name="notifications" size={12} /> {step.sideEffects.notifications}
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
{step.sideEffects.dac && (
|
|
59
|
+
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md"
|
|
60
|
+
style={{ background: 'rgba(139,92,246,0.15)', color: '#8b5cf6' }}
|
|
61
|
+
>
|
|
62
|
+
<Icon name="assignment" size={12} /> {step.sideEffects.dac}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
{step.sideEffects.pubsub && (
|
|
66
|
+
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md"
|
|
67
|
+
style={{ background: 'rgba(16,185,129,0.15)', color: '#10b981' }}
|
|
68
|
+
>
|
|
69
|
+
<Icon name="cell_tower" size={12} /> {step.sideEffects.pubsub}
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FlowStep } from '../../data/types';
|
|
2
|
+
import { Icon } from '../shared/Icon';
|
|
3
|
+
import { ACTOR_ICONS } from '../../utils/iconMap';
|
|
4
|
+
|
|
5
|
+
interface TriggerEventCardProps {
|
|
6
|
+
step: FlowStep;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function TriggerEventCard({ step }: TriggerEventCardProps) {
|
|
10
|
+
const actorIcon = ACTOR_ICONS[step.actor] || 'person';
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
<h4 className="text-xs font-bold text-slate-400 uppercase mb-2 flex items-center gap-2">
|
|
15
|
+
<Icon name="bolt" size={16} /> Trigger Event
|
|
16
|
+
</h4>
|
|
17
|
+
<div className="rounded-lg p-3 border group relative"
|
|
18
|
+
style={{
|
|
19
|
+
background: 'var(--color-surface-darker)',
|
|
20
|
+
borderColor: 'var(--color-surface-highlight)',
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<div className="flex items-center gap-2 mb-2">
|
|
24
|
+
<span className="text-[10px] font-bold px-1.5 rounded text-white"
|
|
25
|
+
style={{ background: 'var(--color-primary)' }}
|
|
26
|
+
>
|
|
27
|
+
{step.actor.toUpperCase()}
|
|
28
|
+
</span>
|
|
29
|
+
<Icon name={actorIcon} size={14} className="text-slate-400" />
|
|
30
|
+
<span className="text-xs text-slate-300 font-mono break-all">{step.trigger}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="text-[10px] text-slate-500 font-mono">
|
|
33
|
+
Status: {step.status}
|
|
34
|
+
</div>
|
|
35
|
+
<button
|
|
36
|
+
onClick={() => navigator.clipboard.writeText(step.trigger)}
|
|
37
|
+
className="absolute top-2 right-2 text-slate-600 hover:text-white opacity-0 group-hover:opacity-100 transition-opacity"
|
|
38
|
+
aria-label="Copy trigger"
|
|
39
|
+
>
|
|
40
|
+
<Icon name="content_copy" size={16} />
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import { Icon } from '../shared/Icon';
|
|
4
|
+
|
|
5
|
+
interface DocPageShellProps {
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
iconColor?: string;
|
|
10
|
+
backTo?: string;
|
|
11
|
+
backLabel?: string;
|
|
12
|
+
headerRight?: ReactNode;
|
|
13
|
+
sidebar?: ReactNode;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function DocPageShell({
|
|
18
|
+
title,
|
|
19
|
+
subtitle,
|
|
20
|
+
icon,
|
|
21
|
+
iconColor = 'var(--color-primary)',
|
|
22
|
+
backTo,
|
|
23
|
+
backLabel = 'Back',
|
|
24
|
+
headerRight,
|
|
25
|
+
sidebar,
|
|
26
|
+
children,
|
|
27
|
+
}: DocPageShellProps) {
|
|
28
|
+
const navigate = useNavigate();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="flex flex-1 overflow-hidden">
|
|
32
|
+
{sidebar && (
|
|
33
|
+
<aside
|
|
34
|
+
className="w-64 flex-none flex flex-col border-r overflow-y-auto"
|
|
35
|
+
style={{
|
|
36
|
+
borderColor: 'var(--color-border-default)',
|
|
37
|
+
background: 'var(--color-bg-primary)',
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{sidebar}
|
|
41
|
+
</aside>
|
|
42
|
+
)}
|
|
43
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
44
|
+
<div
|
|
45
|
+
className="flex-none px-8 py-5 border-b flex items-center justify-between"
|
|
46
|
+
style={{
|
|
47
|
+
borderColor: 'var(--color-border-default)',
|
|
48
|
+
background: 'var(--color-bg-primary)',
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<div className="flex items-center gap-4">
|
|
52
|
+
{backTo && (
|
|
53
|
+
<button
|
|
54
|
+
onClick={() => navigate(backTo)}
|
|
55
|
+
className="flex items-center gap-1 text-slate-400 hover:text-white transition-colors text-sm"
|
|
56
|
+
>
|
|
57
|
+
<Icon name="arrow_back" size={18} />
|
|
58
|
+
{backLabel}
|
|
59
|
+
</button>
|
|
60
|
+
)}
|
|
61
|
+
{icon && (
|
|
62
|
+
<div
|
|
63
|
+
className="w-10 h-10 rounded-lg flex items-center justify-center"
|
|
64
|
+
style={{ background: `${iconColor}20`, color: iconColor }}
|
|
65
|
+
>
|
|
66
|
+
<Icon name={icon} size={22} />
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
<div>
|
|
70
|
+
<h1 className="text-xl font-bold text-white font-display">{title}</h1>
|
|
71
|
+
{subtitle && <p className="text-sm text-slate-400 mt-0.5">{subtitle}</p>}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
{headerRight}
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex-1 overflow-y-auto">{children}</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useState, forwardRef, type ReactNode } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { Icon } from '../shared/Icon';
|
|
4
|
+
|
|
5
|
+
interface DocSectionCardProps {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
iconColor?: string;
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DocSectionCard = forwardRef<HTMLElement, DocSectionCardProps>(
|
|
15
|
+
function DocSectionCard({ id, title, icon, iconColor = 'var(--color-primary)', defaultOpen = true, children }, ref) {
|
|
16
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<section ref={ref} id={id} className="scroll-mt-6">
|
|
20
|
+
<button
|
|
21
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
22
|
+
className="w-full flex items-center justify-between py-3 group"
|
|
23
|
+
>
|
|
24
|
+
<div className="flex items-center gap-3">
|
|
25
|
+
<div
|
|
26
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center"
|
|
27
|
+
style={{ background: `${iconColor}20`, color: iconColor }}
|
|
28
|
+
>
|
|
29
|
+
<Icon name={icon} size={18} />
|
|
30
|
+
</div>
|
|
31
|
+
<h2 className="text-base font-bold text-slate-100 font-display">{title}</h2>
|
|
32
|
+
</div>
|
|
33
|
+
<Icon
|
|
34
|
+
name={isOpen ? 'expand_less' : 'expand_more'}
|
|
35
|
+
size={20}
|
|
36
|
+
className="text-slate-500 group-hover:text-slate-300 transition-colors"
|
|
37
|
+
/>
|
|
38
|
+
</button>
|
|
39
|
+
<AnimatePresence initial={false}>
|
|
40
|
+
{isOpen && (
|
|
41
|
+
<motion.div
|
|
42
|
+
initial={{ height: 0, opacity: 0 }}
|
|
43
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
44
|
+
exit={{ height: 0, opacity: 0 }}
|
|
45
|
+
transition={{ duration: 0.2 }}
|
|
46
|
+
className="overflow-hidden"
|
|
47
|
+
>
|
|
48
|
+
<div className="pb-6">{children}</div>
|
|
49
|
+
</motion.div>
|
|
50
|
+
)}
|
|
51
|
+
</AnimatePresence>
|
|
52
|
+
</section>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { Icon } from '../shared/Icon';
|
|
3
|
+
|
|
4
|
+
interface TocItem {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface DocTableOfContentsProps {
|
|
11
|
+
items: TocItem[];
|
|
12
|
+
roleLabel: string;
|
|
13
|
+
roleIcon: string;
|
|
14
|
+
roleColor: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function DocTableOfContents({ items, roleLabel, roleIcon, roleColor }: DocTableOfContentsProps) {
|
|
18
|
+
const [activeId, setActiveId] = useState(items[0]?.id ?? '');
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const observer = new IntersectionObserver(
|
|
22
|
+
(entries) => {
|
|
23
|
+
const visible = entries.filter((e) => e.isIntersecting);
|
|
24
|
+
if (visible.length > 0) {
|
|
25
|
+
setActiveId(visible[0].target.id);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{ rootMargin: '-80px 0px -60% 0px', threshold: 0.1 }
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
items.forEach(({ id }) => {
|
|
32
|
+
const el = document.getElementById(id);
|
|
33
|
+
if (el) observer.observe(el);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return () => observer.disconnect();
|
|
37
|
+
}, [items]);
|
|
38
|
+
|
|
39
|
+
const scrollTo = (id: string) => {
|
|
40
|
+
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="p-4">
|
|
45
|
+
<div className="flex items-center gap-2 mb-6 px-2">
|
|
46
|
+
<div
|
|
47
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center"
|
|
48
|
+
style={{ background: `${roleColor}20`, color: roleColor }}
|
|
49
|
+
>
|
|
50
|
+
<Icon name={roleIcon} size={18} />
|
|
51
|
+
</div>
|
|
52
|
+
<span className="text-sm font-bold text-white font-display">{roleLabel}</span>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="text-[10px] uppercase tracking-widest text-slate-500 font-semibold px-2 mb-3">
|
|
55
|
+
Sections ({items.length})
|
|
56
|
+
</div>
|
|
57
|
+
<nav className="space-y-0.5">
|
|
58
|
+
{items.map(({ id, title, icon }) => {
|
|
59
|
+
const isActive = activeId === id;
|
|
60
|
+
return (
|
|
61
|
+
<button
|
|
62
|
+
key={id}
|
|
63
|
+
onClick={() => scrollTo(id)}
|
|
64
|
+
className="w-full flex items-center gap-2.5 px-2 py-2 rounded-lg text-left transition-all text-sm"
|
|
65
|
+
style={{
|
|
66
|
+
background: isActive ? `${roleColor}15` : 'transparent',
|
|
67
|
+
color: isActive ? roleColor : 'var(--color-text-muted)',
|
|
68
|
+
borderLeft: isActive ? `2px solid ${roleColor}` : '2px solid transparent',
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Icon name={icon} size={16} />
|
|
72
|
+
<span className="truncate font-medium">{title}</span>
|
|
73
|
+
</button>
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
</nav>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|