@pennyfarthing/cyclist 9.2.0 → 9.4.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/dist/api/hotspots.d.ts +3 -0
- package/dist/api/hotspots.d.ts.map +1 -0
- package/dist/api/hotspots.js +54 -0
- package/dist/api/hotspots.js.map +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/settings.d.ts +1 -1
- package/dist/api/settings.d.ts.map +1 -1
- package/dist/api/settings.js +44 -17
- package/dist/api/settings.js.map +1 -1
- package/dist/main.d.ts +4 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +7 -0
- package/dist/main.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +43 -39
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -1
- package/dist/server.js.map +1 -1
- package/dist/story-parser.d.ts +17 -0
- package/dist/story-parser.d.ts.map +1 -1
- package/dist/story-parser.js +183 -13
- package/dist/story-parser.js.map +1 -1
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +5 -4
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
- package/src/public/App.tsx +2 -0
- package/src/public/components/ControlBar.tsx +1 -1
- package/src/public/components/DockviewWorkspace.tsx +4 -0
- package/src/public/components/FontPicker/index.tsx +118 -33
- package/src/public/components/FullFileTree.tsx +223 -0
- package/src/public/components/Message.tsx +32 -10
- package/src/public/components/MessageView.tsx +176 -93
- package/src/public/components/PersonaHeader.tsx +45 -15
- package/src/public/components/SubagentSpan.tsx +15 -8
- package/src/public/components/ThemePalette/ThemePalette.css +2 -0
- package/src/public/components/ToolStack.tsx +23 -13
- package/src/public/components/panels/AuditLogPanel.tsx +140 -66
- package/src/public/components/panels/ChangedPanel.tsx +30 -44
- package/src/public/components/panels/HotspotsPanel.tsx +365 -0
- package/src/public/components/panels/MessagePanel.tsx +14 -2
- package/src/public/components/panels/SettingsPanel.tsx +10 -10
- package/src/public/components/panels/WorkflowPanel.tsx +85 -12
- package/src/public/components/panels/index.ts +1 -0
- package/src/public/components/ui/switch.tsx +2 -2
- package/src/public/css/theme-system.css +71 -43
- package/src/public/hooks/useFileBrowser.ts +71 -0
- package/src/public/hooks/useHotspots.ts +113 -0
- package/src/public/hooks/useStory.ts +12 -3
- package/src/public/images/cyclist-dark.png +0 -0
- package/src/public/images/cyclist-light.png +0 -0
- package/src/public/styles/tailwind.css +428 -69
- package/src/public/types/message.ts +4 -0
- package/src/public/utils/slash-commands.ts +1 -1
- package/src/public/utils/toolStackGrouper.ts +4 -5
|
@@ -152,26 +152,31 @@
|
|
|
152
152
|
Tool Call Block Styling (MSSCI-13402) - Klinger Redesign
|
|
153
153
|
============================================================================= */
|
|
154
154
|
|
|
155
|
-
/* Base tool call block */
|
|
155
|
+
/* Base tool call block — Tufte: indented, no border box */
|
|
156
156
|
.tool-call-block {
|
|
157
|
-
background
|
|
158
|
-
border-radius:
|
|
159
|
-
border:
|
|
157
|
+
background: none;
|
|
158
|
+
border-radius: 0;
|
|
159
|
+
border: none;
|
|
160
|
+
border-left: 2px solid var(--border);
|
|
160
161
|
overflow: hidden;
|
|
162
|
+
margin-left: 2.75rem; /* align with message content (avatar + gap) */
|
|
163
|
+
margin-top: 0.5rem;
|
|
164
|
+
margin-bottom: 0.5rem;
|
|
165
|
+
padding-left: 0.5rem;
|
|
161
166
|
transition: border-color 0.15s ease;
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
.tool-call-block:hover {
|
|
165
|
-
border-color: var(--accent);
|
|
170
|
+
border-left-color: var(--accent);
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
/* Tool Header -
|
|
173
|
+
/* Tool Header - compact single-line */
|
|
169
174
|
.tool-header {
|
|
170
175
|
display: flex;
|
|
171
176
|
align-items: center;
|
|
172
|
-
gap: 0.
|
|
173
|
-
padding: 0.
|
|
174
|
-
background
|
|
177
|
+
gap: 0.5rem;
|
|
178
|
+
padding: 0.125rem 0;
|
|
179
|
+
background: none;
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
/* Tool Type Badge - colored pill for instant recognition */
|
|
@@ -201,14 +206,14 @@
|
|
|
201
206
|
.tool-call-block.tool-edit .tool-type-badge { background-color: var(--tool-edit-color); }
|
|
202
207
|
.tool-call-block.tool-task .tool-type-badge { background-color: var(--tool-task-color); }
|
|
203
208
|
|
|
204
|
-
/*
|
|
205
|
-
.tool-call-block.tool-read { border-left:
|
|
206
|
-
.tool-call-block.tool-write { border-left:
|
|
207
|
-
.tool-call-block.tool-bash { border-left:
|
|
208
|
-
.tool-call-block.tool-glob { border-left:
|
|
209
|
-
.tool-call-block.tool-grep { border-left:
|
|
210
|
-
.tool-call-block.tool-edit { border-left:
|
|
211
|
-
.tool-call-block.tool-task { border-left:
|
|
209
|
+
/* Color the left border by tool type */
|
|
210
|
+
.tool-call-block.tool-read { border-left-color: var(--tool-read-color); }
|
|
211
|
+
.tool-call-block.tool-write { border-left-color: var(--tool-write-color); }
|
|
212
|
+
.tool-call-block.tool-bash { border-left-color: var(--tool-bash-color); }
|
|
213
|
+
.tool-call-block.tool-glob { border-left-color: var(--tool-glob-color); }
|
|
214
|
+
.tool-call-block.tool-grep { border-left-color: var(--tool-grep-color); }
|
|
215
|
+
.tool-call-block.tool-edit { border-left-color: var(--tool-edit-color); }
|
|
216
|
+
.tool-call-block.tool-task { border-left-color: var(--tool-task-color); }
|
|
212
217
|
|
|
213
218
|
/* Intent summary - the human-readable description */
|
|
214
219
|
.tool-name {
|
|
@@ -238,8 +243,7 @@
|
|
|
238
243
|
|
|
239
244
|
/* Error state styling */
|
|
240
245
|
.tool-call-block.tool-error {
|
|
241
|
-
border-left-color: var(--status-error);
|
|
242
|
-
background-color: rgba(239, 68, 68, 0.05);
|
|
246
|
+
border-left-color: var(--status-error) !important;
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
.tool-call-block.tool-error .tool-type-badge {
|
|
@@ -251,9 +255,8 @@
|
|
|
251
255
|
display: flex;
|
|
252
256
|
align-items: center;
|
|
253
257
|
gap: 0.5rem;
|
|
254
|
-
padding: 0.
|
|
255
|
-
background
|
|
256
|
-
border-top: 1px solid var(--border);
|
|
258
|
+
padding: 0.125rem 0;
|
|
259
|
+
background: none;
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
.tool-result-toggle {
|
|
@@ -391,10 +394,13 @@
|
|
|
391
394
|
|
|
392
395
|
/* Container for grouped tool calls */
|
|
393
396
|
.tool-stack {
|
|
394
|
-
margin: 0.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
397
|
+
margin: 0.5rem 0;
|
|
398
|
+
margin-left: 2.75rem; /* align with message content */
|
|
399
|
+
border-radius: 0;
|
|
400
|
+
background: none;
|
|
401
|
+
border: none;
|
|
402
|
+
border-left: 2px solid var(--border);
|
|
403
|
+
padding-left: 0.5rem;
|
|
398
404
|
}
|
|
399
405
|
|
|
400
406
|
.tool-stack.collapsed {
|
|
@@ -405,21 +411,21 @@
|
|
|
405
411
|
.tool-stack-header {
|
|
406
412
|
display: flex;
|
|
407
413
|
align-items: center;
|
|
408
|
-
gap: 0.
|
|
409
|
-
padding: 0.
|
|
414
|
+
gap: 0.5rem;
|
|
415
|
+
padding: 0.25rem 0;
|
|
410
416
|
cursor: pointer;
|
|
411
417
|
user-select: none;
|
|
412
|
-
background
|
|
413
|
-
border-bottom:
|
|
414
|
-
transition: background-color 0.15s ease
|
|
418
|
+
background: none;
|
|
419
|
+
border-bottom: none;
|
|
420
|
+
transition: background-color 0.15s ease;
|
|
415
421
|
}
|
|
416
422
|
|
|
417
423
|
.tool-stack:not(.collapsed) .tool-stack-header {
|
|
418
|
-
border-bottom-color:
|
|
424
|
+
border-bottom-color: transparent;
|
|
419
425
|
}
|
|
420
426
|
|
|
421
427
|
.tool-stack-header:hover {
|
|
422
|
-
background-color: var(--bg-
|
|
428
|
+
background-color: var(--bg-secondary);
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
.tool-stack-header:focus {
|
|
@@ -438,11 +444,30 @@
|
|
|
438
444
|
transition: transform 0.2s ease;
|
|
439
445
|
}
|
|
440
446
|
|
|
441
|
-
/* Tool count
|
|
442
|
-
.tool-stack-count {
|
|
447
|
+
/* Tool count - small de-emphasized badge */
|
|
448
|
+
.tool-stack-count-badge {
|
|
443
449
|
font-weight: 600;
|
|
450
|
+
font-size: 0.6875rem;
|
|
451
|
+
color: var(--text-muted);
|
|
452
|
+
background-color: var(--bg-tertiary);
|
|
453
|
+
padding: 0.0625rem 0.375rem;
|
|
454
|
+
border-radius: 9999px;
|
|
455
|
+
font-family: var(--font-mono);
|
|
456
|
+
min-width: 1.25rem;
|
|
457
|
+
text-align: center;
|
|
458
|
+
flex-shrink: 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Active tool summary - the main visual element while running */
|
|
462
|
+
.tool-stack-active-summary {
|
|
463
|
+
font-weight: 500;
|
|
444
464
|
font-size: 0.8125rem;
|
|
445
465
|
color: var(--text-primary);
|
|
466
|
+
flex: 1;
|
|
467
|
+
min-width: 0;
|
|
468
|
+
overflow: hidden;
|
|
469
|
+
text-overflow: ellipsis;
|
|
470
|
+
white-space: nowrap;
|
|
446
471
|
}
|
|
447
472
|
|
|
448
473
|
/* Mini tool badges in collapsed header */
|
|
@@ -472,10 +497,11 @@
|
|
|
472
497
|
.tool-mini-badge.badge-edit { background-color: var(--tool-edit-color); }
|
|
473
498
|
.tool-mini-badge.badge-task { background-color: var(--tool-task-color); }
|
|
474
499
|
|
|
475
|
-
/* Summary in collapsed state */
|
|
500
|
+
/* Summary in collapsed state - still prominent */
|
|
476
501
|
.tool-stack-summary {
|
|
477
|
-
color: var(--text-
|
|
478
|
-
font-size: 0.
|
|
502
|
+
color: var(--text-secondary);
|
|
503
|
+
font-size: 0.8125rem;
|
|
504
|
+
font-weight: 500;
|
|
479
505
|
overflow: hidden;
|
|
480
506
|
text-overflow: ellipsis;
|
|
481
507
|
white-space: nowrap;
|
|
@@ -495,14 +521,16 @@
|
|
|
495
521
|
.tool-stack-content {
|
|
496
522
|
display: flex;
|
|
497
523
|
flex-direction: column;
|
|
498
|
-
gap: 0.
|
|
499
|
-
padding: 0.
|
|
500
|
-
background
|
|
524
|
+
gap: 0.125rem;
|
|
525
|
+
padding: 0.25rem 0;
|
|
526
|
+
background: none;
|
|
501
527
|
}
|
|
502
528
|
|
|
503
|
-
/*
|
|
529
|
+
/* Within a stack, tool blocks don't need extra indent or left border */
|
|
504
530
|
.tool-stack-content .tool-call-block {
|
|
505
|
-
margin: 0;
|
|
531
|
+
margin-left: 0;
|
|
532
|
+
border-left: none;
|
|
533
|
+
padding-left: 0;
|
|
506
534
|
}
|
|
507
535
|
|
|
508
536
|
/* Tool state classes within stack */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFileBrowser Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetches directory listings from /api/files for the full file tree.
|
|
5
|
+
* Lazy-loads subdirectories on demand.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from 'react';
|
|
9
|
+
|
|
10
|
+
export interface DirectoryEntry {
|
|
11
|
+
name: string;
|
|
12
|
+
path: string;
|
|
13
|
+
type: 'file' | 'directory';
|
|
14
|
+
isModified?: boolean;
|
|
15
|
+
size?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DirectoryListing {
|
|
19
|
+
path: string;
|
|
20
|
+
entries: DirectoryEntry[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface DirectoryCache {
|
|
24
|
+
[path: string]: DirectoryEntry[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface UseFileBrowserResult {
|
|
28
|
+
cache: DirectoryCache;
|
|
29
|
+
loading: Set<string>;
|
|
30
|
+
error: string | null;
|
|
31
|
+
fetchDirectory: (dirPath: string) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useFileBrowser(): UseFileBrowserResult {
|
|
35
|
+
const [cache, setCache] = useState<DirectoryCache>({});
|
|
36
|
+
const [loading, setLoading] = useState<Set<string>>(new Set());
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
const fetchDirectory = useCallback(async (dirPath: string) => {
|
|
40
|
+
// Already cached or loading
|
|
41
|
+
if (cache[dirPath] || loading.has(dirPath)) return;
|
|
42
|
+
|
|
43
|
+
setLoading(prev => new Set(prev).add(dirPath));
|
|
44
|
+
setError(null);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const params = dirPath ? `?path=${encodeURIComponent(dirPath)}` : '';
|
|
48
|
+
const res = await fetch(`/api/files${params}`);
|
|
49
|
+
if (!res.ok) throw new Error(`Failed to list directory: ${res.statusText}`);
|
|
50
|
+
const listing: DirectoryListing = await res.json();
|
|
51
|
+
|
|
52
|
+
// Sort: directories first, then files, alphabetical within each
|
|
53
|
+
const sorted = listing.entries.sort((a, b) => {
|
|
54
|
+
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
|
|
55
|
+
return a.name.localeCompare(b.name);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
setCache(prev => ({ ...prev, [dirPath || '__root__']: sorted }));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
setError(err instanceof Error ? err.message : 'Failed to load directory');
|
|
61
|
+
} finally {
|
|
62
|
+
setLoading(prev => {
|
|
63
|
+
const next = new Set(prev);
|
|
64
|
+
next.delete(dirPath);
|
|
65
|
+
return next;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}, [cache, loading]);
|
|
69
|
+
|
|
70
|
+
return { cache, loading, error, fetchDirectory };
|
|
71
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// Types matching Python HotspotResult / MultiRepoHotspotResult
|
|
4
|
+
export interface FileHotspot {
|
|
5
|
+
path: string;
|
|
6
|
+
change_count: number;
|
|
7
|
+
bug_fix_count: number;
|
|
8
|
+
author_count: number;
|
|
9
|
+
lines_added: number;
|
|
10
|
+
lines_deleted: number;
|
|
11
|
+
churn: number;
|
|
12
|
+
last_changed: string;
|
|
13
|
+
hotspot_score: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DirectoryHotspot {
|
|
17
|
+
path: string;
|
|
18
|
+
file_count: number;
|
|
19
|
+
total_changes: number;
|
|
20
|
+
total_bug_fixes: number;
|
|
21
|
+
avg_author_count: number;
|
|
22
|
+
hotspot_score: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HotspotRepoResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
repo_name: string;
|
|
28
|
+
repo_path: string;
|
|
29
|
+
time_window_days: number;
|
|
30
|
+
commit_count: number;
|
|
31
|
+
file_hotspots: FileHotspot[];
|
|
32
|
+
directory_hotspots: DirectoryHotspot[];
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HotspotData {
|
|
37
|
+
success: boolean;
|
|
38
|
+
// Single-repo result fields (when --path is used)
|
|
39
|
+
repo_name?: string;
|
|
40
|
+
repo_path?: string;
|
|
41
|
+
time_window_days?: number;
|
|
42
|
+
commit_count?: number;
|
|
43
|
+
file_hotspots?: FileHotspot[];
|
|
44
|
+
directory_hotspots?: DirectoryHotspot[];
|
|
45
|
+
// Multi-repo result fields
|
|
46
|
+
repo_results?: HotspotRepoResult[];
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UseHotspotsOptions {
|
|
51
|
+
days: number;
|
|
52
|
+
repo?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UseHotspotsReturn {
|
|
56
|
+
data: HotspotData | null;
|
|
57
|
+
isLoading: boolean;
|
|
58
|
+
error: Error | null;
|
|
59
|
+
refresh: () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useHotspots(options: UseHotspotsOptions): UseHotspotsReturn {
|
|
63
|
+
const [data, setData] = useState<HotspotData | null>(null);
|
|
64
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
65
|
+
const [error, setError] = useState<Error | null>(null);
|
|
66
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
67
|
+
|
|
68
|
+
const fetchHotspots = useCallback(() => {
|
|
69
|
+
// Cancel any in-flight request
|
|
70
|
+
if (abortRef.current) {
|
|
71
|
+
abortRef.current.abort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
abortRef.current = controller;
|
|
76
|
+
|
|
77
|
+
setIsLoading(true);
|
|
78
|
+
setError(null);
|
|
79
|
+
|
|
80
|
+
const params = new URLSearchParams({ days: String(options.days) });
|
|
81
|
+
if (options.repo) {
|
|
82
|
+
params.set('repo', options.repo);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fetch(`/api/hotspots?${params}`, { signal: controller.signal })
|
|
86
|
+
.then((res) => {
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
return res.json();
|
|
91
|
+
})
|
|
92
|
+
.then((json: HotspotData) => {
|
|
93
|
+
setData(json);
|
|
94
|
+
setIsLoading(false);
|
|
95
|
+
})
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
if (err.name === 'AbortError') return;
|
|
98
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
99
|
+
setIsLoading(false);
|
|
100
|
+
});
|
|
101
|
+
}, [options.days, options.repo]);
|
|
102
|
+
|
|
103
|
+
// Cleanup abort controller on unmount
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
return () => {
|
|
106
|
+
if (abortRef.current) {
|
|
107
|
+
abortRef.current.abort();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
return { data, isLoading, error, refresh: fetchHotspots };
|
|
113
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { useState, useEffect, useRef } from 'react';
|
|
12
12
|
|
|
13
13
|
// Import types from story-parser for criteria and workflow
|
|
14
|
-
import type { CriteriaItem, WorkflowPhase } from '../../../story-parser.js';
|
|
14
|
+
import type { CriteriaItem, WorkflowPhase, AvailableWorkflow } from '../../../story-parser.js';
|
|
15
15
|
|
|
16
16
|
export interface StoryData {
|
|
17
17
|
id: string;
|
|
@@ -24,15 +24,19 @@ export interface StoryData {
|
|
|
24
24
|
// MSSCI-12849: AC and BikeLane panel data
|
|
25
25
|
criteria?: CriteriaItem[] | null;
|
|
26
26
|
workflowPhases?: WorkflowPhase[] | null;
|
|
27
|
+
// MSSCI-14300: Distinguish phased vs stepped workflow rendering
|
|
28
|
+
workflowType?: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
// Re-export types for panel components
|
|
30
|
-
export type { CriteriaItem, WorkflowPhase };
|
|
32
|
+
export type { CriteriaItem, WorkflowPhase, AvailableWorkflow };
|
|
31
33
|
|
|
32
34
|
interface UseStoryResult {
|
|
33
35
|
story: StoryData | null;
|
|
34
36
|
isLoading: boolean;
|
|
35
37
|
error: Error | null;
|
|
38
|
+
// MSSCI-14301: Available workflows for discovery panel
|
|
39
|
+
availableWorkflows: AvailableWorkflow[] | null;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/** WebSocket message format from /ws/story */
|
|
@@ -44,7 +48,9 @@ interface StoryMessage {
|
|
|
44
48
|
status?: string | null;
|
|
45
49
|
points?: number | null;
|
|
46
50
|
workflow?: WorkflowPhase[] | null;
|
|
51
|
+
workflowType?: string | null;
|
|
47
52
|
criteria?: CriteriaItem[] | null;
|
|
53
|
+
availableWorkflows?: AvailableWorkflow[] | null;
|
|
48
54
|
[key: string]: unknown;
|
|
49
55
|
}
|
|
50
56
|
|
|
@@ -59,6 +65,7 @@ function transformMessage(msg: StoryMessage): StoryData | null {
|
|
|
59
65
|
points: msg.points ?? undefined,
|
|
60
66
|
criteria: msg.criteria,
|
|
61
67
|
workflowPhases: msg.workflow,
|
|
68
|
+
workflowType: msg.workflowType ?? undefined,
|
|
62
69
|
};
|
|
63
70
|
}
|
|
64
71
|
|
|
@@ -66,6 +73,7 @@ export function useStory(): UseStoryResult {
|
|
|
66
73
|
const [story, setStory] = useState<StoryData | null>(null);
|
|
67
74
|
const [isLoading, setIsLoading] = useState(true);
|
|
68
75
|
const [error, setError] = useState<Error | null>(null);
|
|
76
|
+
const [availableWorkflows, setAvailableWorkflows] = useState<AvailableWorkflow[] | null>(null);
|
|
69
77
|
const wsRef = useRef<WebSocket | null>(null);
|
|
70
78
|
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
71
79
|
|
|
@@ -86,6 +94,7 @@ export function useStory(): UseStoryResult {
|
|
|
86
94
|
const msg = JSON.parse(event.data) as StoryMessage;
|
|
87
95
|
if (msg.type === 'init' || msg.type === 'update') {
|
|
88
96
|
setStory(transformMessage(msg));
|
|
97
|
+
setAvailableWorkflows(msg.availableWorkflows ?? null);
|
|
89
98
|
setIsLoading(false);
|
|
90
99
|
setError(null);
|
|
91
100
|
}
|
|
@@ -122,5 +131,5 @@ export function useStory(): UseStoryResult {
|
|
|
122
131
|
};
|
|
123
132
|
}, []);
|
|
124
133
|
|
|
125
|
-
return { story, isLoading, error };
|
|
134
|
+
return { story, isLoading, error, availableWorkflows };
|
|
126
135
|
}
|
|
Binary file
|
|
Binary file
|