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,100 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useFlowStore } from "../store/useFlowStore";
|
|
3
|
+
|
|
4
|
+
export function usePlayback() {
|
|
5
|
+
const {
|
|
6
|
+
playbackState,
|
|
7
|
+
speed,
|
|
8
|
+
activeStepIndex,
|
|
9
|
+
animatingMessages,
|
|
10
|
+
nextStep,
|
|
11
|
+
getCurrentSteps,
|
|
12
|
+
setAnimatingMessages,
|
|
13
|
+
addLog,
|
|
14
|
+
tickElapsed,
|
|
15
|
+
resetElapsed,
|
|
16
|
+
} = useFlowStore();
|
|
17
|
+
|
|
18
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
19
|
+
const elapsedRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
20
|
+
const prevStepRef = useRef<number>(-1);
|
|
21
|
+
|
|
22
|
+
// Elapsed time ticker
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (playbackState === 'playing') {
|
|
25
|
+
elapsedRef.current = setInterval(tickElapsed, 100);
|
|
26
|
+
} else {
|
|
27
|
+
if (elapsedRef.current) clearInterval(elapsedRef.current);
|
|
28
|
+
}
|
|
29
|
+
if (playbackState === 'idle') resetElapsed();
|
|
30
|
+
return () => {
|
|
31
|
+
if (elapsedRef.current) clearInterval(elapsedRef.current);
|
|
32
|
+
};
|
|
33
|
+
}, [playbackState]);
|
|
34
|
+
|
|
35
|
+
// Generate logs on step change
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (prevStepRef.current === activeStepIndex) return;
|
|
38
|
+
prevStepRef.current = activeStepIndex;
|
|
39
|
+
|
|
40
|
+
const steps = getCurrentSteps();
|
|
41
|
+
const currentStep = steps[activeStepIndex];
|
|
42
|
+
if (!currentStep) return;
|
|
43
|
+
|
|
44
|
+
addLog({
|
|
45
|
+
level: 'info',
|
|
46
|
+
source: currentStep.actor.toUpperCase(),
|
|
47
|
+
message: `Step ${activeStepIndex + 1}: ${currentStep.title}`,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
for (const msg of currentStep.messages) {
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
addLog({
|
|
53
|
+
level: 'info',
|
|
54
|
+
source: msg.from,
|
|
55
|
+
message: `${msg.type}: ${msg.label} → ${msg.to}`,
|
|
56
|
+
});
|
|
57
|
+
}, msg.delay / speed);
|
|
58
|
+
}
|
|
59
|
+
}, [activeStepIndex, speed]);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (playbackState !== "playing") {
|
|
63
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const steps = getCurrentSteps();
|
|
68
|
+
const currentStep = steps[activeStepIndex];
|
|
69
|
+
if (!currentStep) return;
|
|
70
|
+
|
|
71
|
+
// Calculate total duration for current step messages
|
|
72
|
+
const messages = currentStep.messages;
|
|
73
|
+
const lastMessage = messages[messages.length - 1];
|
|
74
|
+
const totalAnimDuration = lastMessage
|
|
75
|
+
? (lastMessage.delay + lastMessage.duration) / speed
|
|
76
|
+
: 1000 / speed;
|
|
77
|
+
|
|
78
|
+
// Trigger message animations
|
|
79
|
+
if (!animatingMessages) {
|
|
80
|
+
setAnimatingMessages(true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Wait for animations + pause, then advance
|
|
84
|
+
const baseDelay = 3000 / speed;
|
|
85
|
+
const stepDuration = totalAnimDuration + baseDelay;
|
|
86
|
+
|
|
87
|
+
timerRef.current = setTimeout(() => {
|
|
88
|
+
const isLast = activeStepIndex >= steps.length - 1;
|
|
89
|
+
if (!isLast) {
|
|
90
|
+
nextStep();
|
|
91
|
+
} else {
|
|
92
|
+
useFlowStore.getState().pause();
|
|
93
|
+
}
|
|
94
|
+
}, stepDuration);
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
98
|
+
};
|
|
99
|
+
}, [playbackState, activeStepIndex, speed, animatingMessages]);
|
|
100
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useFlowStore } from '../store/useFlowStore';
|
|
3
|
+
import type { StakeholderView } from '../data/types';
|
|
4
|
+
|
|
5
|
+
export function useStakeholderFilter<T extends { visibleTo: StakeholderView[] }>(
|
|
6
|
+
items: T[]
|
|
7
|
+
): T[] {
|
|
8
|
+
const view = useFlowStore((s) => s.stakeholderView);
|
|
9
|
+
return useMemo(() => items.filter((item) => item.visibleTo.includes(view)), [items, view]);
|
|
10
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@layer base {
|
|
4
|
+
:root {
|
|
5
|
+
--color-bg-primary: #141118;
|
|
6
|
+
--color-bg-secondary: #1e1a25;
|
|
7
|
+
--color-bg-tertiary: #2f2938;
|
|
8
|
+
--color-bg-brand-soft: #1a0244;
|
|
9
|
+
--color-bg-accent-soft: #25060e;
|
|
10
|
+
|
|
11
|
+
--color-surface-dark: #1e1a25;
|
|
12
|
+
--color-surface-highlight: #2f2938;
|
|
13
|
+
--color-surface-darker: #0f0e13;
|
|
14
|
+
|
|
15
|
+
--color-text-primary: #ffffff;
|
|
16
|
+
--color-text-secondary: #a8a4b2;
|
|
17
|
+
--color-text-muted: #767284;
|
|
18
|
+
|
|
19
|
+
--color-primary: #6116da;
|
|
20
|
+
--color-primary-hover: #7a2ce0;
|
|
21
|
+
--color-primary-soft: #35087c;
|
|
22
|
+
|
|
23
|
+
--color-accent: #ff6490;
|
|
24
|
+
--color-accent-hover: #fb2576;
|
|
25
|
+
--color-accent-soft: #4f0520;
|
|
26
|
+
|
|
27
|
+
--color-border-light: #453c53;
|
|
28
|
+
--color-border-default: #342e40;
|
|
29
|
+
--color-border-strong: #141118;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
* {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
html, body, #root {
|
|
39
|
+
height: 100%;
|
|
40
|
+
width: 100%;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
body {
|
|
45
|
+
font-family: 'Noto Sans', system-ui, -apple-system, sans-serif;
|
|
46
|
+
background: var(--color-bg-primary);
|
|
47
|
+
color: var(--color-text-primary);
|
|
48
|
+
-webkit-font-smoothing: antialiased;
|
|
49
|
+
-moz-osx-font-smoothing: grayscale;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
h1, h2, h3, h4, h5, h6 {
|
|
53
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
::-webkit-scrollbar {
|
|
57
|
+
width: 8px;
|
|
58
|
+
height: 8px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
::-webkit-scrollbar-track {
|
|
62
|
+
background: var(--color-bg-primary);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
::-webkit-scrollbar-thumb {
|
|
66
|
+
background: var(--color-surface-highlight);
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
::-webkit-scrollbar-thumb:hover {
|
|
71
|
+
background: var(--color-border-light);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Utility classes */
|
|
76
|
+
.glass-panel {
|
|
77
|
+
background: rgba(47, 41, 56, 0.4);
|
|
78
|
+
backdrop-filter: blur(12px);
|
|
79
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.flow-grid {
|
|
83
|
+
background-size: 40px 40px;
|
|
84
|
+
background-image:
|
|
85
|
+
linear-gradient(to right, rgba(255, 255, 255, 0.03) 1px, transparent 1px),
|
|
86
|
+
linear-gradient(to bottom, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.code-block {
|
|
90
|
+
font-family: 'Courier New', Courier, monospace;
|
|
91
|
+
background: var(--color-surface-darker);
|
|
92
|
+
border: 1px solid var(--color-surface-highlight);
|
|
93
|
+
border-radius: 0.5rem;
|
|
94
|
+
padding: 0.75rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.font-display {
|
|
98
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.font-body {
|
|
102
|
+
font-family: 'Noto Sans', sans-serif;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Logs terminal scrollbar */
|
|
106
|
+
.logs-scrollbar::-webkit-scrollbar {
|
|
107
|
+
width: 6px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.logs-scrollbar::-webkit-scrollbar-track {
|
|
111
|
+
background: var(--color-surface-darker);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.logs-scrollbar::-webkit-scrollbar-thumb {
|
|
115
|
+
background: #4a4059;
|
|
116
|
+
border-radius: 10px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.logs-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
120
|
+
background: #5d5270;
|
|
121
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
4
|
+
import './index.css'
|
|
5
|
+
import App from './App'
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById('root')!).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<BrowserRouter basename={import.meta.env.BASE_URL}>
|
|
10
|
+
<App />
|
|
11
|
+
</BrowserRouter>
|
|
12
|
+
</StrictMode>,
|
|
13
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DocPageShell } from '../components/DocLayout/DocPageShell';
|
|
2
|
+
import { RoleCard } from '../components/DocLayout/RoleCard';
|
|
3
|
+
import { ROLES } from '../data/roles';
|
|
4
|
+
|
|
5
|
+
export function RoleSelectPage() {
|
|
6
|
+
return (
|
|
7
|
+
<DocPageShell
|
|
8
|
+
title="Documentation Hub"
|
|
9
|
+
subtitle="Select your role to see tailored documentation for the Booking Status Sync feature"
|
|
10
|
+
icon="menu_book"
|
|
11
|
+
backTo="/"
|
|
12
|
+
backLabel="Dashboard"
|
|
13
|
+
>
|
|
14
|
+
<div className="p-8 max-w-5xl mx-auto">
|
|
15
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
16
|
+
{ROLES.map((role) => (
|
|
17
|
+
<RoleCard key={role.slug} role={role} />
|
|
18
|
+
))}
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
className="mt-8 rounded-xl border p-5 text-center"
|
|
23
|
+
style={{ background: 'rgba(20,17,24,0.3)', borderColor: 'var(--color-border-default)' }}
|
|
24
|
+
>
|
|
25
|
+
<p className="text-sm text-slate-400">
|
|
26
|
+
Each role page includes tailored documentation sections and relevant flow paths.
|
|
27
|
+
<br />
|
|
28
|
+
<span className="text-slate-500">Use the Reference panel (top nav) for quick-access to the legend and rules.</span>
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</DocPageShell>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { DocPageShell } from '../components/DocLayout/DocPageShell';
|
|
4
|
+
import { DocTableOfContents } from '../components/DocLayout/DocTableOfContents';
|
|
5
|
+
import { DocSectionCard } from '../components/DocLayout/DocSectionCard';
|
|
6
|
+
import { getRoleBySlug } from '../data/roles';
|
|
7
|
+
import { DOC_SECTIONS } from '../data/docSections';
|
|
8
|
+
import { useFlowStore } from '../store/useFlowStore';
|
|
9
|
+
import { Icon } from '../components/shared/Icon';
|
|
10
|
+
import type { StakeholderView } from '../data/types';
|
|
11
|
+
|
|
12
|
+
export function StakeholderDocPage() {
|
|
13
|
+
const { roleSlug } = useParams<{ roleSlug: string }>();
|
|
14
|
+
const navigate = useNavigate();
|
|
15
|
+
const setStakeholderView = useFlowStore((s) => s.setStakeholderView);
|
|
16
|
+
|
|
17
|
+
const role = getRoleBySlug(roleSlug ?? '');
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (role) {
|
|
21
|
+
setStakeholderView(role.slug as StakeholderView);
|
|
22
|
+
}
|
|
23
|
+
}, [role, setStakeholderView]);
|
|
24
|
+
|
|
25
|
+
if (!role) {
|
|
26
|
+
return (
|
|
27
|
+
<DocPageShell title="Role Not Found" backTo="/docs" backLabel="Back to Docs">
|
|
28
|
+
<div className="flex items-center justify-center h-full">
|
|
29
|
+
<div className="text-center">
|
|
30
|
+
<Icon name="error_outline" size={48} className="text-slate-600 mb-3" />
|
|
31
|
+
<p className="text-slate-400">Unknown role: {roleSlug}</p>
|
|
32
|
+
<button
|
|
33
|
+
onClick={() => navigate('/docs')}
|
|
34
|
+
className="mt-4 px-4 py-2 rounded-lg text-sm font-medium text-white"
|
|
35
|
+
style={{ background: 'var(--color-primary)' }}
|
|
36
|
+
>
|
|
37
|
+
Back to Documentation Hub
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</DocPageShell>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sections = role.sectionIds
|
|
46
|
+
.map((id) => DOC_SECTIONS[id])
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
|
|
49
|
+
const tocItems = sections.map((s) => ({ id: s.id, title: s.title, icon: s.icon }));
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<DocPageShell
|
|
53
|
+
title={role.label}
|
|
54
|
+
subtitle={role.description}
|
|
55
|
+
icon={role.icon}
|
|
56
|
+
iconColor={role.color}
|
|
57
|
+
backTo="/docs"
|
|
58
|
+
backLabel="All Roles"
|
|
59
|
+
headerRight={
|
|
60
|
+
<button
|
|
61
|
+
onClick={() => navigate('/')}
|
|
62
|
+
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors"
|
|
63
|
+
style={{ background: `${role.color}20`, color: role.color }}
|
|
64
|
+
onMouseEnter={(e) => (e.currentTarget.style.background = `${role.color}30`)}
|
|
65
|
+
onMouseLeave={(e) => (e.currentTarget.style.background = `${role.color}20`)}
|
|
66
|
+
>
|
|
67
|
+
<Icon name="play_circle" size={18} />
|
|
68
|
+
Open Visualizer
|
|
69
|
+
</button>
|
|
70
|
+
}
|
|
71
|
+
sidebar={
|
|
72
|
+
<DocTableOfContents
|
|
73
|
+
items={tocItems}
|
|
74
|
+
roleLabel={role.shortLabel}
|
|
75
|
+
roleIcon={role.icon}
|
|
76
|
+
roleColor={role.color}
|
|
77
|
+
/>
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
<div className="p-8 max-w-4xl space-y-2">
|
|
81
|
+
{sections.map((section) => {
|
|
82
|
+
const Component = section.component;
|
|
83
|
+
return (
|
|
84
|
+
<DocSectionCard
|
|
85
|
+
key={section.id}
|
|
86
|
+
id={section.id}
|
|
87
|
+
title={section.title}
|
|
88
|
+
icon={section.icon}
|
|
89
|
+
iconColor={section.iconColor}
|
|
90
|
+
>
|
|
91
|
+
<Component />
|
|
92
|
+
</DocSectionCard>
|
|
93
|
+
);
|
|
94
|
+
})}
|
|
95
|
+
</div>
|
|
96
|
+
</DocPageShell>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { PATHS } from '../data/paths';
|
|
4
|
+
import { useFlowStore } from '../store/useFlowStore';
|
|
5
|
+
import { Icon } from '../components/shared/Icon';
|
|
6
|
+
|
|
7
|
+
export function SubPathDetailPage() {
|
|
8
|
+
const { pathId } = useParams<{ pathId: string }>();
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
const selectPath = useFlowStore((s) => s.selectPath);
|
|
11
|
+
const [activeTab, setActiveTab] = useState(0);
|
|
12
|
+
|
|
13
|
+
const path = useMemo(() => {
|
|
14
|
+
const id = parseInt(pathId || '0');
|
|
15
|
+
return PATHS.find((p) => p.id === id);
|
|
16
|
+
}, [pathId]);
|
|
17
|
+
|
|
18
|
+
if (!path) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex-1 flex items-center justify-center">
|
|
21
|
+
<div className="text-center">
|
|
22
|
+
<Icon name="error" size={48} className="text-slate-500 mb-4" />
|
|
23
|
+
<h2 className="text-xl font-bold text-white mb-2">Path Not Found</h2>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => navigate('/')}
|
|
26
|
+
className="text-sm px-4 py-2 rounded-lg"
|
|
27
|
+
style={{ background: 'var(--color-primary)', color: 'white' }}
|
|
28
|
+
>
|
|
29
|
+
Go Back
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const subPaths = path.subPaths || [];
|
|
37
|
+
const currentSubPath = subPaths[activeTab];
|
|
38
|
+
const steps = currentSubPath?.steps || path.steps;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<main className="flex-1 flex flex-col overflow-y-auto px-6 py-8 max-w-7xl mx-auto w-full">
|
|
42
|
+
{/* Header */}
|
|
43
|
+
<header className="mb-8">
|
|
44
|
+
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6">
|
|
45
|
+
<div>
|
|
46
|
+
<div className="flex items-center gap-2 mb-2">
|
|
47
|
+
<span className="px-2 py-0.5 rounded text-xs font-bold uppercase tracking-wider"
|
|
48
|
+
style={{ background: `${path.color}25`, color: path.color, border: `1px solid ${path.color}40` }}
|
|
49
|
+
>
|
|
50
|
+
Path {path.id}
|
|
51
|
+
</span>
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => navigate('/')}
|
|
54
|
+
className="text-slate-400 text-sm hover:text-white transition-colors"
|
|
55
|
+
>
|
|
56
|
+
/ Dashboard
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
<h2 className="text-3xl md:text-4xl font-bold text-white font-display mb-2">
|
|
60
|
+
{path.label}
|
|
61
|
+
</h2>
|
|
62
|
+
<p className="text-slate-400 max-w-2xl text-lg">{path.description}</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex gap-3">
|
|
65
|
+
<button className="px-4 py-2 rounded-lg border text-white text-sm font-medium flex items-center gap-2 transition-colors"
|
|
66
|
+
style={{
|
|
67
|
+
background: 'var(--color-surface-dark)',
|
|
68
|
+
borderColor: 'var(--color-border-default)',
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Icon name="download" size={18} />
|
|
72
|
+
Export PDF
|
|
73
|
+
</button>
|
|
74
|
+
<button
|
|
75
|
+
onClick={() => { selectPath(path.id); navigate('/'); }}
|
|
76
|
+
className="px-4 py-2 rounded-lg text-white text-sm font-medium flex items-center gap-2 transition-colors"
|
|
77
|
+
style={{
|
|
78
|
+
background: 'var(--color-primary)',
|
|
79
|
+
boxShadow: '0 4px 15px rgba(97,22,218,0.2)',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<Icon name="play_arrow" size={18} />
|
|
83
|
+
Simulate Path
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</header>
|
|
88
|
+
|
|
89
|
+
{/* Sub-path tabs */}
|
|
90
|
+
{subPaths.length > 0 && (
|
|
91
|
+
<div className="border-b mb-8" style={{ borderColor: 'var(--color-border-default)' }}>
|
|
92
|
+
<div className="flex gap-8 overflow-x-auto pb-px">
|
|
93
|
+
{subPaths.map((sub, idx) => (
|
|
94
|
+
<button
|
|
95
|
+
key={sub.id}
|
|
96
|
+
onClick={() => setActiveTab(idx)}
|
|
97
|
+
className="group flex flex-col items-center gap-3 min-w-[140px] cursor-pointer"
|
|
98
|
+
>
|
|
99
|
+
<span className={`font-medium text-sm ${idx === activeTab ? 'text-[var(--color-primary)] font-bold' : 'text-slate-400 hover:text-white'} transition-colors`}>
|
|
100
|
+
{sub.id}. {sub.label}
|
|
101
|
+
</span>
|
|
102
|
+
<div className={`h-0.5 w-full rounded-t-full ${idx === activeTab ? 'bg-[var(--color-primary)]' : 'bg-transparent hover:bg-[var(--color-border-default)]'} transition-colors`} />
|
|
103
|
+
</button>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{/* Content Grid */}
|
|
110
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
|
111
|
+
{/* Left: System State & Side Effects Table */}
|
|
112
|
+
<div className="lg:col-span-2 flex flex-col gap-6">
|
|
113
|
+
{/* System State */}
|
|
114
|
+
<div className="rounded-xl p-6 border shadow-xl relative overflow-hidden"
|
|
115
|
+
style={{
|
|
116
|
+
background: 'var(--color-surface-dark)',
|
|
117
|
+
borderColor: 'var(--color-border-default)',
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<h3 className="text-xl font-bold text-white mb-6 flex items-center gap-2 font-display">
|
|
121
|
+
<Icon name="schema" size={20} className="text-[var(--color-primary)]" />
|
|
122
|
+
System State Visualization
|
|
123
|
+
</h3>
|
|
124
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
125
|
+
{steps[0]?.activeComponents.slice(0, 3).map((compId, idx) => (
|
|
126
|
+
<div key={compId} className="flex items-center gap-4">
|
|
127
|
+
<div className="rounded-lg p-4 flex flex-col gap-3 flex-1 border"
|
|
128
|
+
style={{
|
|
129
|
+
background: 'var(--color-bg-primary)',
|
|
130
|
+
borderColor: idx === 0 ? 'rgba(97,22,218,0.5)' : 'var(--color-border-default)',
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<div className="text-slate-300 text-sm font-semibold uppercase tracking-wider">
|
|
134
|
+
{compId.replace(/-/g, ' ')}
|
|
135
|
+
</div>
|
|
136
|
+
<div className="text-xs text-slate-500">
|
|
137
|
+
Status: <span className="text-slate-300 font-mono">{steps[0]?.status}</span>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
{idx < 2 && (
|
|
141
|
+
<Icon name="arrow_forward" size={20} className="text-slate-600 hidden md:block shrink-0" />
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{/* Side Effects Table */}
|
|
149
|
+
<div className="rounded-xl border overflow-hidden"
|
|
150
|
+
style={{
|
|
151
|
+
background: 'var(--color-surface-dark)',
|
|
152
|
+
borderColor: 'var(--color-border-default)',
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<div className="px-6 py-4 border-b flex justify-between items-center"
|
|
156
|
+
style={{
|
|
157
|
+
borderColor: 'var(--color-border-default)',
|
|
158
|
+
background: 'var(--color-bg-primary)',
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<h3 className="text-white font-bold text-lg flex items-center gap-2 font-display">
|
|
162
|
+
<Icon name="table_chart" size={20} className="text-slate-400" />
|
|
163
|
+
Scenario Side Effects
|
|
164
|
+
</h3>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="overflow-x-auto">
|
|
167
|
+
<table className="w-full text-left text-sm">
|
|
168
|
+
<thead>
|
|
169
|
+
<tr className="border-b" style={{ borderColor: 'var(--color-border-default)', color: 'var(--color-text-muted)' }}>
|
|
170
|
+
<th className="px-6 py-3 font-medium">Step</th>
|
|
171
|
+
<th className="px-6 py-3 font-medium">Status</th>
|
|
172
|
+
<th className="px-6 py-3 font-medium">Side Effects</th>
|
|
173
|
+
</tr>
|
|
174
|
+
</thead>
|
|
175
|
+
<tbody>
|
|
176
|
+
{steps.map((step) => (
|
|
177
|
+
<tr key={step.id} className="border-t hover:bg-white/5 transition-colors"
|
|
178
|
+
style={{ borderColor: 'var(--color-border-default)' }}
|
|
179
|
+
>
|
|
180
|
+
<td className="px-6 py-4 font-medium text-white">{step.title}</td>
|
|
181
|
+
<td className="px-6 py-4">
|
|
182
|
+
<span className="inline-flex items-center px-2 py-1 rounded text-xs font-medium"
|
|
183
|
+
style={{
|
|
184
|
+
background: 'rgba(97,22,218,0.1)',
|
|
185
|
+
color: 'var(--color-primary)',
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
{step.bookingStatus}
|
|
189
|
+
</span>
|
|
190
|
+
</td>
|
|
191
|
+
<td className="px-6 py-4 text-slate-400">
|
|
192
|
+
{Object.entries(step.sideEffects)
|
|
193
|
+
.filter(([, v]) => v)
|
|
194
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
195
|
+
.join(', ') || 'None'}
|
|
196
|
+
</td>
|
|
197
|
+
</tr>
|
|
198
|
+
))}
|
|
199
|
+
</tbody>
|
|
200
|
+
</table>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{/* Right: Step Detail Panel */}
|
|
206
|
+
<div className="flex flex-col gap-6">
|
|
207
|
+
<div className="rounded-xl p-6 border h-full"
|
|
208
|
+
style={{
|
|
209
|
+
background: 'var(--color-surface-dark)',
|
|
210
|
+
borderColor: 'var(--color-border-default)',
|
|
211
|
+
}}
|
|
212
|
+
>
|
|
213
|
+
<div className="flex items-center justify-between mb-6">
|
|
214
|
+
<h2 className="text-white text-xl font-bold font-display">Step Detail Panel</h2>
|
|
215
|
+
<div className="p-2 rounded-lg" style={{ background: 'rgba(97,22,218,0.2)' }}>
|
|
216
|
+
<Icon name="info" size={20} className="text-[var(--color-primary)]" />
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div className="space-y-6">
|
|
221
|
+
{/* Pre-conditions */}
|
|
222
|
+
<div>
|
|
223
|
+
<h4 className="text-slate-400 text-xs font-bold uppercase tracking-wider mb-2">Pre-conditions</h4>
|
|
224
|
+
<ul className="space-y-2">
|
|
225
|
+
{steps[0] && (
|
|
226
|
+
<>
|
|
227
|
+
<li className="flex items-start gap-2 text-sm text-slate-300">
|
|
228
|
+
<Icon name="check_circle" size={18} className="text-emerald-400 shrink-0" />
|
|
229
|
+
<span>Status is <code className="px-1 rounded text-xs" style={{ background: 'var(--color-bg-primary)', color: 'var(--color-primary)' }}>{steps[0].status}</code></span>
|
|
230
|
+
</li>
|
|
231
|
+
<li className="flex items-start gap-2 text-sm text-slate-300">
|
|
232
|
+
<Icon name="check_circle" size={18} className="text-emerald-400 shrink-0" />
|
|
233
|
+
<span>Trigger: {steps[0].trigger}</span>
|
|
234
|
+
</li>
|
|
235
|
+
</>
|
|
236
|
+
)}
|
|
237
|
+
</ul>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div className="h-px w-full" style={{ background: 'var(--color-border-default)' }} />
|
|
241
|
+
|
|
242
|
+
{/* Critical Logic */}
|
|
243
|
+
<div>
|
|
244
|
+
<h4 className="text-slate-400 text-xs font-bold uppercase tracking-wider mb-2">Critical Logic</h4>
|
|
245
|
+
<div className="p-4 rounded-r-lg border-l-2"
|
|
246
|
+
style={{
|
|
247
|
+
background: 'var(--color-bg-primary)',
|
|
248
|
+
borderLeftColor: 'var(--color-primary)',
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
<p className="text-sm text-slate-300 leading-relaxed">
|
|
252
|
+
{steps[0]?.description || 'No description available.'}
|
|
253
|
+
</p>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
{/* Component Flags */}
|
|
258
|
+
{steps[0] && (
|
|
259
|
+
<div>
|
|
260
|
+
<h4 className="text-slate-400 text-xs font-bold uppercase tracking-wider mb-2">Component Flags</h4>
|
|
261
|
+
<div className="grid grid-cols-2 gap-3">
|
|
262
|
+
{steps[0].activeComponents.map((comp) => (
|
|
263
|
+
<div key={comp} className="p-3 rounded border"
|
|
264
|
+
style={{
|
|
265
|
+
background: 'var(--color-bg-primary)',
|
|
266
|
+
borderColor: 'var(--color-border-default)',
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
<div className="text-xs text-slate-500 mb-1">{comp}</div>
|
|
270
|
+
<div className="text-emerald-400 font-mono text-sm">ACTIVE</div>
|
|
271
|
+
</div>
|
|
272
|
+
))}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</main>
|
|
281
|
+
);
|
|
282
|
+
}
|