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.
Files changed (107) hide show
  1. package/CHANGELOG.md +2 -11
  2. package/README.md +30 -5
  3. package/bin/yad.mjs +36 -1
  4. package/cli/docs.mjs +298 -0
  5. package/cli/manifest.mjs +6 -1
  6. package/cli/roster.mjs +164 -0
  7. package/cli/setup.mjs +128 -2
  8. package/package.json +3 -4
  9. package/skills/sdlc/config.yaml +19 -0
  10. package/skills/sdlc/install.sh +1 -1
  11. package/skills/sdlc/module-help.csv +4 -0
  12. package/skills/yad-connect-docs/SKILL.md +132 -0
  13. package/skills/yad-connect-docs/references/docs-registry.md +74 -0
  14. package/skills/yad-connect-repos/SKILL.md +4 -0
  15. package/skills/yad-connect-repos/references/hub-config.md +3 -1
  16. package/skills/yad-docs/SKILL.md +159 -0
  17. package/skills/yad-docs/references/data-mapping.md +75 -0
  18. package/skills/yad-docs/references/theme-map.md +69 -0
  19. package/skills/yad-docs/templates/app/README.md +31 -0
  20. package/skills/yad-docs/templates/app/eslint.config.js +23 -0
  21. package/skills/yad-docs/templates/app/index.html +17 -0
  22. package/skills/yad-docs/templates/app/package-lock.json +4030 -0
  23. package/skills/yad-docs/templates/app/package.json +35 -0
  24. package/skills/yad-docs/templates/app/public/favicon.svg +28 -0
  25. package/skills/yad-docs/templates/app/public/logo.svg +39 -0
  26. package/skills/yad-docs/templates/app/public/vite.svg +1 -0
  27. package/skills/yad-docs/templates/app/src/App.tsx +98 -0
  28. package/skills/yad-docs/templates/app/src/components/Auth/LoginPage.tsx +101 -0
  29. package/skills/yad-docs/templates/app/src/components/Canvas/AnimatedMessage.tsx +101 -0
  30. package/skills/yad-docs/templates/app/src/components/Canvas/ConnectionLine.tsx +90 -0
  31. package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +216 -0
  32. package/skills/yad-docs/templates/app/src/components/Canvas/SystemComponent.tsx +153 -0
  33. package/skills/yad-docs/templates/app/src/components/Controls/PlaybackBar.tsx +284 -0
  34. package/skills/yad-docs/templates/app/src/components/Controls/StepDetail.tsx +167 -0
  35. package/skills/yad-docs/templates/app/src/components/DetailPanel/HandlerLogicSnippet.tsx +41 -0
  36. package/skills/yad-docs/templates/app/src/components/DetailPanel/RequestPayloadPreview.tsx +46 -0
  37. package/skills/yad-docs/templates/app/src/components/DetailPanel/RightPanel.tsx +88 -0
  38. package/skills/yad-docs/templates/app/src/components/DetailPanel/StatusCard.tsx +76 -0
  39. package/skills/yad-docs/templates/app/src/components/DetailPanel/TriggerEventCard.tsx +45 -0
  40. package/skills/yad-docs/templates/app/src/components/DocLayout/DocPageShell.tsx +80 -0
  41. package/skills/yad-docs/templates/app/src/components/DocLayout/DocSectionCard.tsx +55 -0
  42. package/skills/yad-docs/templates/app/src/components/DocLayout/DocTableOfContents.tsx +79 -0
  43. package/skills/yad-docs/templates/app/src/components/DocLayout/RoleCard.tsx +67 -0
  44. package/skills/yad-docs/templates/app/src/components/DocSections/ApiReferenceSection.tsx +108 -0
  45. package/skills/yad-docs/templates/app/src/components/DocSections/CancelabilitySection.tsx +73 -0
  46. package/skills/yad-docs/templates/app/src/components/DocSections/CriticalRunbookSection.tsx +177 -0
  47. package/skills/yad-docs/templates/app/src/components/DocSections/DataMigrationSection.tsx +102 -0
  48. package/skills/yad-docs/templates/app/src/components/DocSections/DbSchemaSection.tsx +98 -0
  49. package/skills/yad-docs/templates/app/src/components/DocSections/DeploymentGuideSection.tsx +104 -0
  50. package/skills/yad-docs/templates/app/src/components/DocSections/DriverIntegrationSection.tsx +127 -0
  51. package/skills/yad-docs/templates/app/src/components/DocSections/ExecutiveSummarySection.tsx +69 -0
  52. package/skills/yad-docs/templates/app/src/components/DocSections/FlowOverviewSection.tsx +73 -0
  53. package/skills/yad-docs/templates/app/src/components/DocSections/FlowPathsChecklistSection.tsx +96 -0
  54. package/skills/yad-docs/templates/app/src/components/DocSections/MiddlewareChainSection.tsx +107 -0
  55. package/skills/yad-docs/templates/app/src/components/DocSections/MonitoringAlertingSection.tsx +106 -0
  56. package/skills/yad-docs/templates/app/src/components/DocSections/NotificationLocalizationSection.tsx +102 -0
  57. package/skills/yad-docs/templates/app/src/components/DocSections/PMRoadmapSection.tsx +133 -0
  58. package/skills/yad-docs/templates/app/src/components/DocSections/PerformanceTestingSection.tsx +91 -0
  59. package/skills/yad-docs/templates/app/src/components/DocSections/RiderIntegrationSection.tsx +99 -0
  60. package/skills/yad-docs/templates/app/src/components/DocSections/SecuritySection.tsx +74 -0
  61. package/skills/yad-docs/templates/app/src/components/DocSections/StatusMachineSection.tsx +90 -0
  62. package/skills/yad-docs/templates/app/src/components/DocSections/TestPlanSection.tsx +163 -0
  63. package/skills/yad-docs/templates/app/src/components/Logs/SystemLogsTerminal.tsx +126 -0
  64. package/skills/yad-docs/templates/app/src/components/Navigation/TopNavBar.tsx +90 -0
  65. package/skills/yad-docs/templates/app/src/components/Reference/BullMQJobsList.tsx +60 -0
  66. package/skills/yad-docs/templates/app/src/components/Reference/DecisionTreeView.tsx +49 -0
  67. package/skills/yad-docs/templates/app/src/components/Reference/DeeplinkActionsChips.tsx +69 -0
  68. package/skills/yad-docs/templates/app/src/components/Reference/DriverUIStatesTable.tsx +61 -0
  69. package/skills/yad-docs/templates/app/src/components/Reference/FeatureFlagMatrix.tsx +73 -0
  70. package/skills/yad-docs/templates/app/src/components/Reference/RiderUIStatesTable.tsx +61 -0
  71. package/skills/yad-docs/templates/app/src/components/Reference/RulesLegendPanel.tsx +217 -0
  72. package/skills/yad-docs/templates/app/src/components/Reference/StakeholderToggle.tsx +41 -0
  73. package/skills/yad-docs/templates/app/src/components/Reference/TroubleshootingSection.tsx +93 -0
  74. package/skills/yad-docs/templates/app/src/components/Sidebar/PathSelector.tsx +148 -0
  75. package/skills/yad-docs/templates/app/src/components/Sidebar/SidebarFooter.tsx +40 -0
  76. package/skills/yad-docs/templates/app/src/components/Sidebar/StepList.tsx +234 -0
  77. package/skills/yad-docs/templates/app/src/components/shared/Badge.tsx +28 -0
  78. package/skills/yad-docs/templates/app/src/components/shared/CommandPalette.tsx +213 -0
  79. package/skills/yad-docs/templates/app/src/components/shared/Icon.tsx +21 -0
  80. package/skills/yad-docs/templates/app/src/components/shared/Tooltip.tsx +42 -0
  81. package/skills/yad-docs/templates/app/src/data/components.ts +74 -0
  82. package/skills/yad-docs/templates/app/src/data/docSections.ts +231 -0
  83. package/skills/yad-docs/templates/app/src/data/paths.ts +2319 -0
  84. package/skills/yad-docs/templates/app/src/data/referenceData.ts +392 -0
  85. package/skills/yad-docs/templates/app/src/data/roles.ts +145 -0
  86. package/skills/yad-docs/templates/app/src/data/types.ts +79 -0
  87. package/skills/yad-docs/templates/app/src/hooks/useAnimationQueue.ts +41 -0
  88. package/skills/yad-docs/templates/app/src/hooks/usePlayback.ts +100 -0
  89. package/skills/yad-docs/templates/app/src/hooks/useStakeholderFilter.ts +10 -0
  90. package/skills/yad-docs/templates/app/src/index.css +121 -0
  91. package/skills/yad-docs/templates/app/src/main.tsx +13 -0
  92. package/skills/yad-docs/templates/app/src/pages/RoleSelectPage.tsx +34 -0
  93. package/skills/yad-docs/templates/app/src/pages/StakeholderDocPage.tsx +98 -0
  94. package/skills/yad-docs/templates/app/src/pages/SubPathDetailPage.tsx +282 -0
  95. package/skills/yad-docs/templates/app/src/store/useAuthStore.ts +42 -0
  96. package/skills/yad-docs/templates/app/src/store/useFlowStore.ts +197 -0
  97. package/skills/yad-docs/templates/app/src/utils/iconMap.ts +46 -0
  98. package/skills/yad-docs/templates/app/tsconfig.app.json +28 -0
  99. package/skills/yad-docs/templates/app/tsconfig.json +7 -0
  100. package/skills/yad-docs/templates/app/tsconfig.node.json +26 -0
  101. package/skills/yad-docs/templates/app/vite.config.ts +10 -0
  102. package/skills/yad-docs-overview/SKILL.md +131 -0
  103. package/skills/yad-docs-overview/references/pipeline-model.md +102 -0
  104. package/skills/yad-docs-sync/SKILL.md +99 -0
  105. package/skills/yad-docs-sync/references/staleness.md +81 -0
  106. package/skills/yad-hub-bridge/references/login-roster.md +1 -0
  107. package/docs/index.html +0 -1323
@@ -0,0 +1,90 @@
1
+ import { Icon } from '../shared/Icon';
2
+ import { STATUS_MAPPINGS } from '../../data/referenceData';
3
+
4
+ const CATEGORY_COLORS: Record<string, string> = {
5
+ Completion: '#22c55e',
6
+ 'Rider cancel': '#ef4444',
7
+ 'Driver cancel': '#f97316',
8
+ 'System cancel': '#eab308',
9
+ 'Active trip cancel': '#ec4899',
10
+ 'Driver confirm': '#06b6d4',
11
+ 'Assignment (special)': '#8b5cf6',
12
+ };
13
+
14
+ const TERMINAL_STATUSES = [
15
+ 'TRIP_BOOK_FINISHED',
16
+ 'TRIP_BOOK_OPERATION_RIDER_CANCELED',
17
+ 'TRIP_BOOK_OPERATION_DRIVER_CANCELED',
18
+ 'TRIP_BOOK_SYSTEM_CANCELED',
19
+ ];
20
+
21
+ export function StatusMachineSection() {
22
+ return (
23
+ <div className="space-y-5">
24
+ <div
25
+ className="rounded-xl border overflow-hidden"
26
+ style={{ borderColor: 'var(--color-border-default)', background: 'rgba(20,17,24,0.5)' }}
27
+ >
28
+ <div className="px-4 py-3 border-b flex items-center justify-between" style={{ borderColor: 'var(--color-border-default)', background: 'rgba(255,255,255,0.03)' }}>
29
+ <span className="text-sm font-bold text-slate-200">TRIP_STATUS → BOOKING_STATUS Mapping</span>
30
+ <span className="text-[10px] font-bold text-slate-400">{STATUS_MAPPINGS.length} entries</span>
31
+ </div>
32
+ <table className="w-full text-sm">
33
+ <thead>
34
+ <tr style={{ background: 'rgba(255,255,255,0.05)' }}>
35
+ <th className="px-4 py-2.5 text-left text-xs font-semibold text-slate-400">Trip Status</th>
36
+ <th className="px-4 py-2.5 text-left text-xs font-semibold text-slate-400">Booking Status</th>
37
+ <th className="px-4 py-2.5 text-right text-xs font-semibold text-slate-400">Category</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ {STATUS_MAPPINGS.map((m) => {
42
+ const color = CATEGORY_COLORS[m.category] || '#64748b';
43
+ return (
44
+ <tr
45
+ key={m.tripStatus}
46
+ className="border-t hover:bg-white/5 transition-colors"
47
+ style={{ borderColor: 'var(--color-border-default)' }}
48
+ >
49
+ <td className="px-4 py-2.5 font-mono text-xs text-slate-300">{m.tripStatus}</td>
50
+ <td className="px-4 py-2.5 font-mono text-xs text-slate-300">{m.bookingStatus}</td>
51
+ <td className="px-4 py-2.5 text-right">
52
+ <span
53
+ className="text-[10px] font-medium px-2 py-0.5 rounded border"
54
+ style={{ color, background: `${color}15`, borderColor: `${color}25` }}
55
+ >
56
+ {m.category}
57
+ </span>
58
+ </td>
59
+ </tr>
60
+ );
61
+ })}
62
+ </tbody>
63
+ </table>
64
+ </div>
65
+
66
+ <div
67
+ className="rounded-xl border p-4"
68
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
69
+ >
70
+ <h4 className="text-sm font-bold text-slate-200 mb-3">Terminal States</h4>
71
+ <p className="text-xs text-slate-400 mb-3">
72
+ Once a booking reaches a terminal state, no further transitions are allowed. The <code className="text-xs bg-white/5 px-1 rounded text-slate-300">fnUpdateBookingStatus</code> middleware
73
+ includes a guard that skips updates if the current status is terminal.
74
+ </p>
75
+ <div className="flex flex-wrap gap-2">
76
+ {TERMINAL_STATUSES.map((s) => (
77
+ <span
78
+ key={s}
79
+ className="px-2.5 py-1 rounded-md text-xs font-mono border"
80
+ style={{ background: 'rgba(239,68,68,0.1)', borderColor: 'rgba(239,68,68,0.2)', color: '#f87171' }}
81
+ >
82
+ <Icon name="block" size={12} className="inline mr-1" />
83
+ {s}
84
+ </span>
85
+ ))}
86
+ </div>
87
+ </div>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,163 @@
1
+ import { useState } from 'react';
2
+ import { Icon } from '../shared/Icon';
3
+
4
+ interface TestCase {
5
+ id: string;
6
+ scenario: string;
7
+ steps: string[];
8
+ expected: string;
9
+ priority: 'critical' | 'high' | 'medium';
10
+ }
11
+
12
+ const PRIORITY_COLORS = {
13
+ critical: { color: '#ef4444', bg: 'rgba(239,68,68,0.1)' },
14
+ high: { color: '#f59e0b', bg: 'rgba(245,158,11,0.1)' },
15
+ medium: { color: '#60a5fa', bg: 'rgba(96,165,250,0.1)' },
16
+ };
17
+
18
+ const TEST_CASES: TestCase[] = [
19
+ {
20
+ id: 'TC-001',
21
+ scenario: 'Happy Path — Rider confirms booking',
22
+ steps: ['Create scheduled trip', 'Wait for confirmation notification (24h before)', 'Rider sends BOOK_CONFIRMED', 'Verify booking_status = TRIP_BOOK_RIDER_CONFIRMED'],
23
+ expected: 'Status transitions correctly, notification sent to driver, history updated',
24
+ priority: 'critical',
25
+ },
26
+ {
27
+ id: 'TC-002',
28
+ scenario: 'Rider declines confirmation',
29
+ steps: ['Create scheduled trip', 'Wait for confirmation notification', 'Rider sends BOOK_UNCONFIRMED', 'Verify booking_status = TRIP_BOOK_RIDER_CANCELED'],
30
+ expected: 'Booking cancelled, BullMQ jobs cleaned up, DAC deleted, driver notified',
31
+ priority: 'critical',
32
+ },
33
+ {
34
+ id: 'TC-003',
35
+ scenario: 'Rider confirmation timeout → ops timeout → system cancel',
36
+ steps: ['Create trip', 'Let rider confirmation expire (30 min)', 'Let ops window expire (15 min)', 'Verify auto-cancellation'],
37
+ expected: 'booking_status = TRIP_BOOK_SYSTEM_CANCELED after full timeout chain',
38
+ priority: 'critical',
39
+ },
40
+ {
41
+ id: 'TC-004',
42
+ scenario: 'Driver accepts booking',
43
+ steps: ['Create trip with assigned driver', 'Driver sends BOOK_ACCEPTED', 'Verify booking_status = TRIP_BOOK_OPERATION_DRIVER_CONFIRMED'],
44
+ expected: 'Status updated, rider notified, DAC card updated',
45
+ priority: 'high',
46
+ },
47
+ {
48
+ id: 'TC-005',
49
+ scenario: 'Driver cancels booking',
50
+ steps: ['Create trip with assigned driver', 'Driver sends BOOK_DRIVER_CANCELED', 'Verify re-dispatch triggered'],
51
+ expected: 'Status = TRIP_BOOK_OPERATION_DRIVER_CANCELED, ride re-dispatched, rider notified',
52
+ priority: 'high',
53
+ },
54
+ {
55
+ id: 'TC-006',
56
+ scenario: 'Concurrent update (optimistic lock)',
57
+ steps: ['Send two booking status updates simultaneously for same trip', 'Verify only one succeeds'],
58
+ expected: 'One update returns success, other silently skips (modifiedCount: 0)',
59
+ priority: 'high',
60
+ },
61
+ {
62
+ id: 'TC-007',
63
+ scenario: 'Invalid transition rejected',
64
+ steps: ['Set booking_status to TRIP_BOOK_FINISHED (terminal)', 'Attempt to send BOOK_CONFIRMED'],
65
+ expected: 'HTTP 409 — INVALID_BOOKING_STATUS_TRANSITION',
66
+ priority: 'medium',
67
+ },
68
+ {
69
+ id: 'TC-008',
70
+ scenario: 'Active trip cancellation (DRIVER_COMING)',
71
+ steps: ['Trip reaches DRIVER_COMING status', 'Rider cancels active trip', 'Verify booking_status sync'],
72
+ expected: 'booking_status = TRIP_BOOK_OPERATION_RIDER_CANCELED via fnUpdateBookingStatus',
73
+ priority: 'high',
74
+ },
75
+ {
76
+ id: 'TC-009',
77
+ scenario: 'Feature flags OFF — no jobs scheduled',
78
+ steps: ['Set ENABLE_BOOKING_JOBS=false', 'Create scheduled trip', 'Verify no BullMQ jobs created'],
79
+ expected: 'Trip created normally but no confirmation jobs in queue',
80
+ priority: 'medium',
81
+ },
82
+ {
83
+ id: 'TC-010',
84
+ scenario: 'Non-booked trip — endpoints return 404',
85
+ steps: ['Create regular (non-scheduled) trip', 'Call PUT /rider/trips/:id/booking-status'],
86
+ expected: 'HTTP 404 — NOT_A_BOOKED_TRIP',
87
+ priority: 'medium',
88
+ },
89
+ ];
90
+
91
+ export function TestPlanSection() {
92
+ const [expandedId, setExpandedId] = useState<string | null>(null);
93
+
94
+ return (
95
+ <div className="space-y-4">
96
+ <div
97
+ className="rounded-xl border p-4"
98
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
99
+ >
100
+ <div className="flex items-center justify-between mb-2">
101
+ <span className="text-sm font-bold text-slate-200">Test Coverage</span>
102
+ <div className="flex gap-3">
103
+ {(['critical', 'high', 'medium'] as const).map((p) => {
104
+ const count = TEST_CASES.filter((t) => t.priority === p).length;
105
+ const pc = PRIORITY_COLORS[p];
106
+ return (
107
+ <span key={p} className="text-[10px] font-bold px-2 py-0.5 rounded" style={{ background: pc.bg, color: pc.color }}>
108
+ {p}: {count}
109
+ </span>
110
+ );
111
+ })}
112
+ </div>
113
+ </div>
114
+ <p className="text-xs text-slate-400">{TEST_CASES.length} test cases covering all flow paths, edge cases, and error scenarios.</p>
115
+ </div>
116
+
117
+ <div className="space-y-2">
118
+ {TEST_CASES.map((tc) => {
119
+ const isExpanded = expandedId === tc.id;
120
+ const pc = PRIORITY_COLORS[tc.priority];
121
+ return (
122
+ <div
123
+ key={tc.id}
124
+ className="rounded-lg border overflow-hidden"
125
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
126
+ >
127
+ <button
128
+ onClick={() => setExpandedId(isExpanded ? null : tc.id)}
129
+ className="w-full px-4 py-3 flex items-center gap-3 hover:bg-white/5 transition-colors"
130
+ >
131
+ <span className="text-[10px] font-mono font-bold text-slate-500 w-14 shrink-0">{tc.id}</span>
132
+ <span className="text-[10px] font-bold px-1.5 py-0.5 rounded shrink-0" style={{ background: pc.bg, color: pc.color }}>
133
+ {tc.priority}
134
+ </span>
135
+ <span className="text-sm text-slate-200 text-left flex-1">{tc.scenario}</span>
136
+ <Icon name={isExpanded ? 'expand_less' : 'expand_more'} size={16} className="text-slate-500" />
137
+ </button>
138
+ {isExpanded && (
139
+ <div className="px-4 pb-3 space-y-3 border-t" style={{ borderColor: 'var(--color-border-default)' }}>
140
+ <div className="pt-3">
141
+ <span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Steps</span>
142
+ <ol className="mt-1.5 space-y-1">
143
+ {tc.steps.map((step, i) => (
144
+ <li key={i} className="flex items-start gap-2">
145
+ <span className="text-[10px] font-bold text-slate-600 w-4 shrink-0 mt-0.5">{i + 1}.</span>
146
+ <span className="text-xs text-slate-400">{step}</span>
147
+ </li>
148
+ ))}
149
+ </ol>
150
+ </div>
151
+ <div>
152
+ <span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Expected Result</span>
153
+ <p className="text-xs text-emerald-400 mt-1">{tc.expected}</p>
154
+ </div>
155
+ </div>
156
+ )}
157
+ </div>
158
+ );
159
+ })}
160
+ </div>
161
+ </div>
162
+ );
163
+ }
@@ -0,0 +1,126 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useFlowStore } from '../../store/useFlowStore';
3
+ import { Icon } from '../shared/Icon';
4
+
5
+ const LEVEL_COLORS: Record<string, string> = {
6
+ info: '#22c55e',
7
+ warn: '#eab308',
8
+ error: '#ef4444',
9
+ debug: '#64748b',
10
+ };
11
+
12
+ function isHighlightEntry(message: string): boolean {
13
+ const keywords = ['request', 'matching', 'confirmation', 'assigned', 'accepted'];
14
+ const lower = message.toLowerCase();
15
+ return keywords.some((k) => lower.includes(k));
16
+ }
17
+
18
+ function exportLogs(logs: { timestamp: Date; level: string; source: string; message: string }[]) {
19
+ const text = logs
20
+ .map((l) => `[${l.timestamp.toLocaleTimeString('en-US', { hour12: false })}] [${l.level.toUpperCase()}] ${l.source}: ${l.message}`)
21
+ .join('\n');
22
+ const blob = new Blob([text], { type: 'text/plain' });
23
+ const url = URL.createObjectURL(blob);
24
+ const a = document.createElement('a');
25
+ a.href = url;
26
+ a.download = `system-logs-${Date.now()}.txt`;
27
+ a.click();
28
+ URL.revokeObjectURL(url);
29
+ }
30
+
31
+ export function SystemLogsTerminal() {
32
+ const logs = useFlowStore((s) => s.logs);
33
+ const clearLogs = useFlowStore((s) => s.clearLogs);
34
+ const toggleLogsPanel = useFlowStore((s) => s.toggleLogsPanel);
35
+ const scrollRef = useRef<HTMLDivElement>(null);
36
+
37
+ useEffect(() => {
38
+ if (scrollRef.current) {
39
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
40
+ }
41
+ }, [logs]);
42
+
43
+ return (
44
+ <div
45
+ className="h-full flex flex-col overflow-hidden"
46
+ style={{ background: 'var(--color-surface-darker, #0f0e13)' }}
47
+ >
48
+ {/* Header */}
49
+ <div
50
+ className="px-4 py-2 border-b flex justify-between items-center"
51
+ style={{
52
+ borderColor: 'var(--color-border-default)',
53
+ background: 'var(--color-surface-dark)',
54
+ }}
55
+ >
56
+ <div className="flex items-center gap-2">
57
+ <Icon name="terminal" size={16} className="text-[var(--color-primary)]" />
58
+ <h3 className="font-mono text-xs font-semibold text-slate-200">System Logs</h3>
59
+ </div>
60
+ <div className="flex items-center gap-3">
61
+ <button
62
+ onClick={() => exportLogs(logs)}
63
+ className="text-[10px] text-slate-400 hover:text-white transition-colors px-2 py-0.5 rounded flex items-center gap-1"
64
+ style={{ background: 'rgba(255,255,255,0.05)' }}
65
+ >
66
+ <Icon name="download" size={12} />
67
+ Export
68
+ </button>
69
+ <button
70
+ onClick={clearLogs}
71
+ className="text-[10px] text-slate-400 hover:text-white transition-colors px-2 py-0.5 rounded"
72
+ style={{ background: 'rgba(255,255,255,0.05)' }}
73
+ >
74
+ Clear
75
+ </button>
76
+ {/* macOS window dots */}
77
+ <div className="flex items-center gap-1.5 ml-1">
78
+ <div className="w-2.5 h-2.5 rounded-full" style={{ background: 'rgba(239,68,68,0.2)', border: '1px solid rgba(239,68,68,0.5)' }} />
79
+ <div className="w-2.5 h-2.5 rounded-full" style={{ background: 'rgba(234,179,8,0.2)', border: '1px solid rgba(234,179,8,0.5)' }} />
80
+ <div className="w-2.5 h-2.5 rounded-full" style={{ background: 'rgba(34,197,94,0.2)', border: '1px solid rgba(34,197,94,0.5)' }} />
81
+ </div>
82
+ <button
83
+ onClick={toggleLogsPanel}
84
+ className="text-slate-400 hover:text-white transition-colors"
85
+ >
86
+ <Icon name="close" size={16} />
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ {/* Log entries */}
92
+ <div
93
+ ref={scrollRef}
94
+ className="flex-1 p-3 font-mono text-xs overflow-y-auto space-y-0.5 logs-scrollbar"
95
+ >
96
+ {logs.length === 0 ? (
97
+ <div className="text-slate-500 animate-pulse">Waiting for events...</div>
98
+ ) : (
99
+ logs.map((log, index) => {
100
+ const isLast = index === logs.length - 1;
101
+ const isHighlight = isHighlightEntry(log.message);
102
+
103
+ return (
104
+ <div
105
+ key={log.id}
106
+ className={`flex gap-3 px-2 py-0.5 rounded transition-all ${isLast ? 'animate-pulse' : ''}`}
107
+ style={{
108
+ color: LEVEL_COLORS[log.level] || '#64748b',
109
+ background: isHighlight ? 'rgba(97,22,218,0.08)' : 'transparent',
110
+ borderLeft: isHighlight ? '2px solid var(--color-primary)' : '2px solid transparent',
111
+ opacity: index < logs.length - 3 ? 0.7 : 1,
112
+ }}
113
+ >
114
+ <span className="w-16 shrink-0 text-slate-600">
115
+ {log.timestamp.toLocaleTimeString('en-US', { hour12: false })}
116
+ </span>
117
+ <span style={{ color: LEVEL_COLORS[log.level] }}>{log.source}</span>
118
+ <span className="text-slate-300">{log.message}</span>
119
+ </div>
120
+ );
121
+ })
122
+ )}
123
+ </div>
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,90 @@
1
+ import { useNavigate } from 'react-router-dom';
2
+ import { Icon } from '../shared/Icon';
3
+ import { useFlowStore } from '../../store/useFlowStore';
4
+ import { useAuthStore } from '../../store/useAuthStore';
5
+
6
+ export function TopNavBar() {
7
+ const navigate = useNavigate();
8
+ const toggleReferencePanel = useFlowStore((s) => s.toggleReferencePanel);
9
+ const toggleCommandPalette = useFlowStore((s) => s.toggleCommandPalette);
10
+ const logout = useAuthStore((s) => s.logout);
11
+
12
+ return (
13
+ <header className="flex-none flex items-center justify-between whitespace-nowrap border-b px-6 py-3 z-20"
14
+ style={{
15
+ borderColor: 'var(--color-border-default)',
16
+ background: 'var(--color-bg-primary)',
17
+ }}
18
+ >
19
+ <div className="flex items-center gap-8">
20
+ <div className="flex items-center gap-3 text-white">
21
+ <img src="/logo.svg" alt="Logo" className="h-8" />
22
+ </div>
23
+ <button
24
+ onClick={toggleCommandPalette}
25
+ className="hidden md:flex items-center rounded-lg px-3 py-1.5 w-64 border transition-colors"
26
+ style={{
27
+ background: 'var(--color-surface-highlight)',
28
+ borderColor: 'transparent',
29
+ }}
30
+ >
31
+ <Icon name="search" size={20} className="text-slate-400" />
32
+ <span className="text-sm text-slate-500 ml-2 flex-1 text-left">Search flows...</span>
33
+ <div className="text-xs text-slate-500 px-1.5 py-0.5 rounded border"
34
+ style={{ background: 'var(--color-surface-dark)', borderColor: 'var(--color-border-default)' }}
35
+ >
36
+ &#8984;K
37
+ </div>
38
+ </button>
39
+ </div>
40
+ <div className="flex items-center gap-4">
41
+ <div className="hidden lg:flex items-center gap-2">
42
+ <button
43
+ onClick={toggleReferencePanel}
44
+ className="flex items-center justify-center px-4 py-2 rounded-full text-slate-300 text-sm font-medium transition-colors"
45
+ style={{ ':hover': { background: 'var(--color-surface-highlight)' } } as React.CSSProperties}
46
+ onMouseEnter={(e) => e.currentTarget.style.background = 'var(--color-surface-highlight)'}
47
+ onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
48
+ >
49
+ <Icon name="menu_book" size={18} className="mr-2" />
50
+ Reference
51
+ </button>
52
+ <button
53
+ onClick={() => navigate('/docs')}
54
+ className="flex items-center justify-center px-4 py-2 rounded-full text-slate-300 text-sm font-medium transition-colors"
55
+ onMouseEnter={(e) => e.currentTarget.style.background = 'var(--color-surface-highlight)'}
56
+ onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
57
+ >
58
+ <Icon name="description" size={18} className="mr-2" />
59
+ Docs
60
+ </button>
61
+ <button
62
+ className="flex items-center justify-center px-4 py-2 rounded-full text-slate-300 text-sm font-medium transition-colors"
63
+ onMouseEnter={(e) => e.currentTarget.style.background = 'var(--color-surface-highlight)'}
64
+ onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
65
+ >
66
+ <Icon name="settings" size={18} className="mr-2" />
67
+ Settings
68
+ </button>
69
+ </div>
70
+ <div className="h-6 w-px mx-2" style={{ background: 'var(--color-surface-highlight)' }} />
71
+ <div className="flex items-center gap-3">
72
+ <div className="text-right hidden sm:block">
73
+ <p className="text-sm font-medium text-white">AbdelRahman Nasr</p>
74
+ <p className="text-xs text-slate-400">Admin</p>
75
+ </div>
76
+ <button
77
+ onClick={logout}
78
+ title="Sign out"
79
+ className="h-10 w-10 rounded-full ring-2 ring-[#2f2938] flex items-center justify-center cursor-pointer transition-opacity hover:opacity-80"
80
+ style={{
81
+ background: 'linear-gradient(135deg, var(--color-primary) 0%, #a855f7 100%)',
82
+ }}
83
+ >
84
+ <Icon name="logout" size={20} className="text-white" />
85
+ </button>
86
+ </div>
87
+ </div>
88
+ </header>
89
+ );
90
+ }
@@ -0,0 +1,60 @@
1
+ import { Icon } from '../shared/Icon';
2
+ import { useStakeholderFilter } from '../../hooks/useStakeholderFilter';
3
+ import { BULLMQ_JOBS } from '../../data/referenceData';
4
+
5
+ export function BullMQJobsList() {
6
+ const jobs = useStakeholderFilter(BULLMQ_JOBS);
7
+
8
+ if (jobs.length === 0) return null;
9
+
10
+ return (
11
+ <section>
12
+ <div className="flex items-center gap-2 mb-4">
13
+ <Icon name="schedule" size={20} className="text-purple-400" />
14
+ <h3 className="text-slate-100 text-lg font-bold font-display">BullMQ Jobs Scheduling</h3>
15
+ </div>
16
+ <div className="space-y-2">
17
+ {jobs.map((job) => (
18
+ <div
19
+ key={job.name}
20
+ className="p-3 rounded-lg border hover:border-purple-500/30 transition-colors"
21
+ style={{
22
+ background: 'rgba(20,17,24,0.5)',
23
+ borderColor: 'var(--color-border-default)',
24
+ }}
25
+ >
26
+ <div className="flex items-start gap-3">
27
+ <div
28
+ className="flex items-center justify-center w-8 h-8 rounded-lg shrink-0 mt-0.5"
29
+ style={{ background: 'rgba(168,85,247,0.15)', color: '#a855f7' }}
30
+ >
31
+ <Icon name="timer" size={18} />
32
+ </div>
33
+ <div className="flex-1 min-w-0">
34
+ <div className="flex items-center gap-2 mb-1">
35
+ <span className="text-sm font-semibold text-slate-200 font-mono">{job.name}</span>
36
+ </div>
37
+ <p className="text-xs text-slate-400 mb-1.5">{job.description}</p>
38
+ <div className="flex flex-wrap gap-2">
39
+ <span
40
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium border"
41
+ style={{ color: '#a855f7', background: 'rgba(168,85,247,0.1)', borderColor: 'rgba(168,85,247,0.2)' }}
42
+ >
43
+ <Icon name="schedule" size={10} />
44
+ {job.timing}
45
+ </span>
46
+ <span
47
+ className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium text-slate-400 border"
48
+ style={{ background: 'rgba(255,255,255,0.03)', borderColor: 'var(--color-border-default)' }}
49
+ >
50
+ queue: {job.queue}
51
+ </span>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ ))}
57
+ </div>
58
+ </section>
59
+ );
60
+ }
@@ -0,0 +1,49 @@
1
+ import { Icon } from '../shared/Icon';
2
+ import { useStakeholderFilter } from '../../hooks/useStakeholderFilter';
3
+ import { DECISION_TREE } from '../../data/referenceData';
4
+
5
+ export function DecisionTreeView() {
6
+ const branches = useStakeholderFilter(DECISION_TREE);
7
+
8
+ if (branches.length === 0) return null;
9
+
10
+ return (
11
+ <section>
12
+ <div className="flex items-center gap-2 mb-4">
13
+ <Icon name="account_tree" size={20} className="text-[var(--color-primary)]" />
14
+ <h3 className="text-slate-100 text-lg font-bold font-display">handleBookAssigned Decision Tree</h3>
15
+ </div>
16
+ <div
17
+ className="rounded-xl border overflow-hidden font-mono text-sm"
18
+ style={{
19
+ borderColor: 'var(--color-border-default)',
20
+ background: 'rgba(20,17,24,0.5)',
21
+ }}
22
+ >
23
+ {branches.map((branch, i) => (
24
+ <div
25
+ key={i}
26
+ className="p-4 border-b last:border-b-0 hover:bg-white/5 transition-colors"
27
+ style={{ borderColor: 'var(--color-border-default)' }}
28
+ >
29
+ <div className="flex items-start gap-2">
30
+ <span className="text-blue-400 shrink-0 mt-0.5">
31
+ {i === branches.length - 1 ? '└' : '├'}
32
+ </span>
33
+ <div className="flex-1 min-w-0">
34
+ <div className="text-slate-200">
35
+ {branch.condition}
36
+ <span className="text-slate-500"> → </span>
37
+ <span className="text-emerald-400">{branch.result}</span>
38
+ </div>
39
+ {branch.detail && (
40
+ <p className="text-[11px] text-slate-500 mt-1.5 font-sans leading-relaxed">{branch.detail}</p>
41
+ )}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ ))}
46
+ </div>
47
+ </section>
48
+ );
49
+ }
@@ -0,0 +1,69 @@
1
+ import { Icon } from '../shared/Icon';
2
+ import { useStakeholderFilter } from '../../hooks/useStakeholderFilter';
3
+ import { DEEPLINK_ACTIONS } from '../../data/referenceData';
4
+
5
+ export function DeeplinkActionsChips() {
6
+ const actions = useStakeholderFilter(DEEPLINK_ACTIONS);
7
+
8
+ if (actions.length === 0) return null;
9
+
10
+ const riderActions = actions.filter((a) => a.target === 'rider');
11
+ const driverActions = actions.filter((a) => a.target === 'driver');
12
+
13
+ return (
14
+ <section>
15
+ <div className="flex items-center gap-2 mb-4">
16
+ <Icon name="link" size={20} className="text-cyan-400" />
17
+ <h3 className="text-slate-100 text-lg font-bold font-display">Deeplink Actions</h3>
18
+ </div>
19
+
20
+ {riderActions.length > 0 && (
21
+ <div className="mb-3">
22
+ <span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold mb-2 block">
23
+ Rider ({riderActions.length})
24
+ </span>
25
+ <div className="flex flex-wrap gap-1.5">
26
+ {riderActions.map((action) => (
27
+ <span
28
+ key={action.value}
29
+ className="px-2.5 py-1.5 rounded-md text-xs font-mono border cursor-default hover:bg-white/5 transition-colors"
30
+ style={{
31
+ background: 'rgba(20,17,24,0.5)',
32
+ borderColor: 'var(--color-border-default)',
33
+ color: '#94a3b8',
34
+ }}
35
+ title={action.description}
36
+ >
37
+ {action.value}
38
+ </span>
39
+ ))}
40
+ </div>
41
+ </div>
42
+ )}
43
+
44
+ {driverActions.length > 0 && (
45
+ <div>
46
+ <span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold mb-2 block">
47
+ Driver ({driverActions.length})
48
+ </span>
49
+ <div className="flex flex-wrap gap-1.5">
50
+ {driverActions.map((action) => (
51
+ <span
52
+ key={action.value}
53
+ className="px-2.5 py-1.5 rounded-md text-xs font-mono border cursor-default hover:bg-white/5 transition-colors"
54
+ style={{
55
+ background: 'rgba(20,17,24,0.5)',
56
+ borderColor: 'var(--color-border-default)',
57
+ color: '#94a3b8',
58
+ }}
59
+ title={action.description}
60
+ >
61
+ {action.value}
62
+ </span>
63
+ ))}
64
+ </div>
65
+ </div>
66
+ )}
67
+ </section>
68
+ );
69
+ }