@corbits/react-ui 0.1.0 → 0.3.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/LICENSE +174 -499
- package/README.md +228 -85
- package/dist/blocks/login/auth-layout.d.ts +1 -1
- package/dist/blocks/login/auth-layout.js +3 -33
- package/dist/blocks/login/login-form.js +6 -119
- package/dist/hooks/use-command-palette-navigation.js +18 -17
- package/dist/hooks/use-controllable-state.d.ts +2 -2
- package/dist/hooks/use-controllable-state.js +23 -15
- package/dist/hooks/use-delayed-autofocus.js +10 -11
- package/dist/hooks/use-dismissable-popover.d.ts +2 -2
- package/dist/hooks/use-dismissable-popover.js +21 -29
- package/dist/hooks/use-list-selection.js +54 -85
- package/dist/hooks/use-prefers-reduced-motion.js +8 -4
- package/dist/hooks/use-scroll-current-into-view.js +5 -7
- package/dist/index.d.ts +0 -11
- package/dist/index.js +10 -19
- package/dist/lib/artifact.js +16 -35
- package/dist/lib/chart-geometry.js +33 -20
- package/dist/lib/chart-palette.js +17 -31
- package/dist/lib/chat-message.js +32 -13
- package/dist/lib/chat-parts.js +2 -1
- package/dist/lib/csv.js +42 -26
- package/dist/lib/metrics.js +30 -48
- package/dist/lib/now-item.js +26 -25
- package/dist/lib/relative-time.js +14 -29
- package/dist/lib/theme.js +32 -30
- package/dist/lib/url.js +9 -8
- package/dist/lib/workflow-display-flow.js +37 -33
- package/dist/lib/workflow-registry.d.ts +0 -41
- package/dist/lib/workflow-registry.js +13 -23
- package/dist/lib/workflow-run-error.js +16 -20
- package/dist/lib/workflow-run-progress.js +94 -52
- package/dist/lib/workflow-run.js +17 -13
- package/dist/styles.css +257 -19
- package/dist/test/render-hook.js +11 -12
- package/dist/theme.css +92 -14
- package/dist/ui/agent-turn.d.ts +1 -1
- package/dist/ui/agent-turn.js +4 -37
- package/dist/ui/animated-number.js +15 -28
- package/dist/ui/approval-card.js +5 -90
- package/dist/ui/artifact-body.js +47 -125
- package/dist/ui/artifact-notice.d.ts +1 -1
- package/dist/ui/artifact-notice.js +9 -16
- package/dist/ui/avatar.d.ts +21 -4
- package/dist/ui/avatar.js +40 -49
- package/dist/ui/badge.js +4 -12
- package/dist/ui/bar-chart.d.ts +1 -1
- package/dist/ui/bar-chart.js +22 -74
- package/dist/ui/block-card.js +7 -45
- package/dist/ui/boot-screen.d.ts +1 -1
- package/dist/ui/boot-screen.js +4 -33
- package/dist/ui/bulk-action-bar.d.ts +1 -1
- package/dist/ui/bulk-action-bar.js +14 -33
- package/dist/ui/button.js +10 -24
- package/dist/ui/card.js +5 -25
- package/dist/ui/category-bars.js +14 -59
- package/dist/ui/chart-frame.d.ts +2 -2
- package/dist/ui/chart-frame.js +6 -85
- package/dist/ui/chat-composer.js +4 -16
- package/dist/ui/chat-dock-timing.js +7 -3
- package/dist/ui/chat-input.js +20 -100
- package/dist/ui/chat-panel.d.ts +1 -1
- package/dist/ui/chat-panel.js +7 -41
- package/dist/ui/chat-thread.js +13 -31
- package/dist/ui/checkbox.js +10 -52
- package/dist/ui/command-palette.js +85 -246
- package/dist/ui/compare-body.d.ts +1 -1
- package/dist/ui/compare-body.js +28 -98
- package/dist/ui/confirm-button.js +8 -15
- package/dist/ui/corbits-mark.js +7 -16
- package/dist/ui/csv-table.d.ts +1 -1
- package/dist/ui/csv-table.js +8 -50
- package/dist/ui/dialog.d.ts +1 -1
- package/dist/ui/dialog.js +32 -62
- package/dist/ui/dictation-status-line.js +7 -25
- package/dist/ui/dither-canvas.d.ts +1 -1
- package/dist/ui/dither-canvas.js +31 -56
- package/dist/ui/embed-body.d.ts +1 -1
- package/dist/ui/embed-body.js +7 -56
- package/dist/ui/empty-state.d.ts +1 -1
- package/dist/ui/empty-state.js +3 -26
- package/dist/ui/file-input.js +17 -37
- package/dist/ui/filter-bar.d.ts +1 -1
- package/dist/ui/filter-bar.js +11 -71
- package/dist/ui/filter-chip.d.ts +1 -1
- package/dist/ui/filter-chip.js +7 -15
- package/dist/ui/gate-block.d.ts +2 -1
- package/dist/ui/gate-block.js +4 -29
- package/dist/ui/horizontal-stepper.d.ts +1 -1
- package/dist/ui/horizontal-stepper.js +18 -45
- package/dist/ui/input.js +1 -6
- package/dist/ui/inspector-shell.d.ts +5 -5
- package/dist/ui/inspector-shell.js +14 -81
- package/dist/ui/intake-form.d.ts +1 -1
- package/dist/ui/intake-form.js +21 -97
- package/dist/ui/kind-card-grid.js +9 -31
- package/dist/ui/library-page-header.js +4 -32
- package/dist/ui/list-detail.js +5 -35
- package/dist/ui/live-run-banner.d.ts +1 -1
- package/dist/ui/live-run-banner.js +5 -15
- package/dist/ui/live-status-line.js +6 -24
- package/dist/ui/menu.js +7 -24
- package/dist/ui/message-bubble.d.ts +3 -3
- package/dist/ui/message-bubble.js +5 -44
- package/dist/ui/mic-button.d.ts +1 -1
- package/dist/ui/mic-button.js +15 -21
- package/dist/ui/mic-permission-dialog.js +5 -41
- package/dist/ui/notifications-bell.js +9 -43
- package/dist/ui/now-cards.d.ts +2 -2
- package/dist/ui/now-cards.js +16 -94
- package/dist/ui/page-panel.js +11 -11
- package/dist/ui/page-shell.js +4 -11
- package/dist/ui/parts-renderer.js +51 -149
- package/dist/ui/profile-card.js +7 -111
- package/dist/ui/progress-checklist.js +11 -49
- package/dist/ui/provider-mark.d.ts +2 -2
- package/dist/ui/provider-mark.js +9 -16
- package/dist/ui/quick-reply-chips.js +6 -14
- package/dist/ui/quote-card.js +12 -32
- package/dist/ui/reasoning-block.d.ts +1 -1
- package/dist/ui/reasoning-block.js +14 -33
- package/dist/ui/research-body.js +32 -213
- package/dist/ui/rich-empty-state.js +6 -48
- package/dist/ui/run-now-button.js +14 -15
- package/dist/ui/section.d.ts +1 -1
- package/dist/ui/section.js +4 -40
- package/dist/ui/select.js +1 -15
- package/dist/ui/selection-checkbox.d.ts +1 -1
- package/dist/ui/selection-checkbox.js +11 -29
- package/dist/ui/settings-panel.js +7 -50
- package/dist/ui/shimmer-text.js +3 -6
- package/dist/ui/sidebar-item-row.d.ts +1 -1
- package/dist/ui/sidebar-item-row.js +9 -39
- package/dist/ui/sidebar-panel.js +15 -40
- package/dist/ui/sidebar.js +15 -76
- package/dist/ui/skeleton.js +3 -7
- package/dist/ui/sortable-table.js +29 -72
- package/dist/ui/sparkline.js +14 -38
- package/dist/ui/stat-grid.js +17 -52
- package/dist/ui/status-dot.d.ts +1 -1
- package/dist/ui/status-dot.js +8 -20
- package/dist/ui/step-list.d.ts +1 -0
- package/dist/ui/step-list.js +11 -50
- package/dist/ui/switch.js +4 -16
- package/dist/ui/table.js +7 -32
- package/dist/ui/tabs.js +39 -54
- package/dist/ui/textarea.js +10 -19
- package/dist/ui/theme-provider.js +54 -78
- package/dist/ui/theme-toggle.js +13 -16
- package/dist/ui/thinking-indicator.js +3 -15
- package/dist/ui/thinking-label.js +16 -35
- package/dist/ui/thinking-mark.d.ts +1 -1
- package/dist/ui/thinking-mark.js +4 -145
- package/dist/ui/time-series-chart.js +43 -174
- package/dist/ui/toast.js +12 -20
- package/dist/ui/token-mosaic.js +5 -43
- package/dist/ui/tool-block.js +18 -70
- package/dist/ui/tool-narrative.d.ts +1 -1
- package/dist/ui/tool-narrative.js +10 -57
- package/dist/ui/tool-picker.js +57 -145
- package/dist/ui/tooltip.d.ts +1 -1
- package/dist/ui/tooltip.js +4 -33
- package/dist/ui/top-bar.js +14 -72
- package/dist/ui/trace-waterfall.d.ts +1 -1
- package/dist/ui/trace-waterfall.js +21 -93
- package/dist/ui/typing-indicator.d.ts +1 -1
- package/dist/ui/typing-indicator.js +4 -18
- package/dist/ui/view-toggle.js +10 -29
- package/dist/ui/voice-waveform.js +3 -11
- package/package.json +96 -535
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
import { classifyRunError } from "./workflow-run-error.js";
|
|
2
|
-
/** Label colour for a step's status, shared by every stepper/sidebar rendering. */
|
|
3
|
-
|
|
4
|
-
if (status === "
|
|
2
|
+
/** Label colour for a step's status, shared by every stepper/sidebar rendering. */
|
|
3
|
+
export function workflowStepLabelClass(status) {
|
|
4
|
+
if (status === "current")
|
|
5
|
+
return "text-foreground";
|
|
6
|
+
if (status === "failed")
|
|
7
|
+
return "text-destructive";
|
|
5
8
|
return "text-muted-foreground";
|
|
6
9
|
}
|
|
7
|
-
/** Mark glyph for a step's status: a checkmark, an exclamation, or its number. */
|
|
8
|
-
|
|
9
|
-
if (status === "
|
|
10
|
+
/** Mark glyph for a step's status: a checkmark, an exclamation, or its number. */
|
|
11
|
+
export function workflowStepGlyph(status, number) {
|
|
12
|
+
if (status === "completed")
|
|
13
|
+
return "✓";
|
|
14
|
+
if (status === "failed")
|
|
15
|
+
return "!";
|
|
10
16
|
return String(number);
|
|
11
17
|
}
|
|
12
18
|
export function getStepPhase(progress, stepId) {
|
|
13
19
|
return progress?.steps.get(stepId)?.phase;
|
|
14
20
|
}
|
|
15
21
|
export function isStepRunning(phase) {
|
|
16
|
-
return phase === "in-flight" ||
|
|
22
|
+
return (phase === "in-flight" ||
|
|
23
|
+
phase === "awaiting-signal" ||
|
|
24
|
+
phase === "awaiting-timer");
|
|
17
25
|
}
|
|
18
|
-
/** The aggregate phase of a display step from its runtime steps. */
|
|
19
|
-
|
|
26
|
+
/** The aggregate phase of a display step from its runtime steps. */
|
|
27
|
+
export function displayStepPhase(progress, stepIds) {
|
|
28
|
+
if (stepIds.length === 0)
|
|
29
|
+
return undefined;
|
|
20
30
|
const terminal = stepIds[stepIds.length - 1];
|
|
21
|
-
if (terminal !== undefined &&
|
|
22
|
-
|
|
31
|
+
if (terminal !== undefined &&
|
|
32
|
+
getStepPhase(progress, terminal) === "completed")
|
|
33
|
+
return "completed";
|
|
34
|
+
for (const id of stepIds) {
|
|
23
35
|
const phase = getStepPhase(progress, id);
|
|
24
|
-
if (isStepRunning(phase))
|
|
36
|
+
if (isStepRunning(phase))
|
|
37
|
+
return phase;
|
|
25
38
|
}
|
|
26
|
-
for (const id of stepIds){
|
|
27
|
-
if (getStepPhase(progress, id) === "failed")
|
|
39
|
+
for (const id of stepIds) {
|
|
40
|
+
if (getStepPhase(progress, id) === "failed")
|
|
41
|
+
return "failed";
|
|
28
42
|
}
|
|
29
|
-
for (const id of stepIds){
|
|
30
|
-
if (getStepPhase(progress, id) !== undefined)
|
|
43
|
+
for (const id of stepIds) {
|
|
44
|
+
if (getStepPhase(progress, id) !== undefined)
|
|
45
|
+
return "in-flight";
|
|
31
46
|
}
|
|
32
47
|
return undefined;
|
|
33
48
|
}
|
|
34
49
|
function hasAnyProgress(progress, step) {
|
|
35
|
-
return step.stepIds.some((id)=>getStepPhase(progress, id) !== undefined);
|
|
50
|
+
return step.stepIds.some((id) => getStepPhase(progress, id) !== undefined);
|
|
36
51
|
}
|
|
37
52
|
/**
|
|
38
53
|
* Index of the display step the run is currently on. A step whose own phase
|
|
@@ -40,15 +55,22 @@ function hasAnyProgress(progress, step) {
|
|
|
40
55
|
* running branch has progressed — independent steps can run concurrently, so
|
|
41
56
|
* a later step's progress says nothing about whether this one failed.
|
|
42
57
|
* Returns the last index once every step is complete.
|
|
43
|
-
*/
|
|
44
|
-
|
|
58
|
+
*/
|
|
59
|
+
export function activeDisplayStepIndex(progress, steps) {
|
|
60
|
+
for (let i = 0; i < steps.length; i++) {
|
|
45
61
|
const step = steps[i];
|
|
46
|
-
if (step === undefined)
|
|
62
|
+
if (step === undefined)
|
|
63
|
+
continue;
|
|
47
64
|
const phase = displayStepPhase(progress, step.stepIds);
|
|
48
|
-
if (phase === "completed")
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
if (phase === "completed")
|
|
66
|
+
continue;
|
|
67
|
+
if (phase === "failed")
|
|
68
|
+
return i;
|
|
69
|
+
const laterProgressed = steps
|
|
70
|
+
.slice(i + 1)
|
|
71
|
+
.some((later) => hasAnyProgress(progress, later));
|
|
72
|
+
if (laterProgressed)
|
|
73
|
+
continue;
|
|
52
74
|
return i;
|
|
53
75
|
}
|
|
54
76
|
return Math.max(0, steps.length - 1);
|
|
@@ -59,25 +81,30 @@ export function buildStepperSteps(progress, steps) {
|
|
|
59
81
|
// behind the run's own terminal phase, and without this it would linger
|
|
60
82
|
// as "current" on an already-finished run.
|
|
61
83
|
const runCompleted = progress?.phase === "completed";
|
|
62
|
-
return steps.map((step, i)=>{
|
|
84
|
+
return steps.map((step, i) => {
|
|
63
85
|
let status;
|
|
64
86
|
if (runCompleted || i < activeIdx) {
|
|
65
87
|
status = "completed";
|
|
66
|
-
}
|
|
88
|
+
}
|
|
89
|
+
else if (i > activeIdx) {
|
|
67
90
|
status = "pending";
|
|
68
|
-
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
69
93
|
const phase = displayStepPhase(progress, step.stepIds);
|
|
70
|
-
status =
|
|
94
|
+
status =
|
|
95
|
+
phase === "completed"
|
|
96
|
+
? "completed"
|
|
97
|
+
: phase === "failed"
|
|
98
|
+
? "failed"
|
|
99
|
+
: "current";
|
|
71
100
|
}
|
|
72
|
-
return {
|
|
73
|
-
number: i + 1,
|
|
74
|
-
label: step.label,
|
|
75
|
-
status
|
|
76
|
-
};
|
|
101
|
+
return { number: i + 1, label: step.label, status };
|
|
77
102
|
});
|
|
78
103
|
}
|
|
79
|
-
/** The active display step, or `null` while the run is terminal. */
|
|
80
|
-
|
|
104
|
+
/** The active display step, or `null` while the run is terminal. */
|
|
105
|
+
export function activeDisplayStep(progress, steps) {
|
|
106
|
+
if (steps.length === 0)
|
|
107
|
+
return null;
|
|
81
108
|
return steps[activeDisplayStepIndex(progress, steps)] ?? null;
|
|
82
109
|
}
|
|
83
110
|
/**
|
|
@@ -87,33 +114,45 @@ export function buildStepperSteps(progress, steps) {
|
|
|
87
114
|
* active group is done or parked on a human gate, or when the active step
|
|
88
115
|
* carries no `activityLabel` at all (a gate or intake step): silence there is
|
|
89
116
|
* correct, never a stepper noun standing in for it.
|
|
90
|
-
*/
|
|
91
|
-
|
|
92
|
-
if (progress
|
|
117
|
+
*/
|
|
118
|
+
export function liveStatusLabel(progress, steps) {
|
|
119
|
+
if (progress === null)
|
|
120
|
+
return null;
|
|
121
|
+
if (progress.phase === "failed" || progress.phase === "completed")
|
|
122
|
+
return null;
|
|
93
123
|
const idx = activeDisplayStepIndex(progress, steps);
|
|
94
|
-
if (idx === 0)
|
|
124
|
+
if (idx === 0)
|
|
125
|
+
return null;
|
|
95
126
|
const step = steps[idx];
|
|
96
|
-
if (step === undefined)
|
|
127
|
+
if (step === undefined)
|
|
128
|
+
return null;
|
|
97
129
|
const phase = displayStepPhase(progress, step.stepIds);
|
|
98
|
-
if (phase === "completed" || phase === "awaiting-signal")
|
|
130
|
+
if (phase === "completed" || phase === "awaiting-signal")
|
|
131
|
+
return null;
|
|
99
132
|
return step.activityLabel ?? null;
|
|
100
133
|
}
|
|
101
134
|
/** Label of the first display step whose aggregate phase is `failed`. Does
|
|
102
135
|
* not require `progress.phase === "failed"` — a caller flagging failure from
|
|
103
|
-
* a single failed step ahead of the run's own phase flip still gets a name. */
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
136
|
+
* a single failed step ahead of the run's own phase flip still gets a name. */
|
|
137
|
+
export function failedDisplayStepLabel(progress, steps) {
|
|
138
|
+
if (progress === null)
|
|
139
|
+
return null;
|
|
140
|
+
for (const step of steps) {
|
|
141
|
+
if (displayStepPhase(progress, step.stepIds) === "failed")
|
|
142
|
+
return step.label;
|
|
107
143
|
}
|
|
108
144
|
return null;
|
|
109
145
|
}
|
|
110
146
|
/** The sanitized end-user message from whichever step failed the run, or
|
|
111
147
|
* `null` if the run has not failed or no step carries an error. Uses the
|
|
112
148
|
* first error found in map-iteration order — a run design that attaches an
|
|
113
|
-
* error to more than one step at once is a design this cannot disambiguate. */
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
149
|
+
* error to more than one step at once is a design this cannot disambiguate. */
|
|
150
|
+
export function failedRunErrorMessage(progress) {
|
|
151
|
+
if (progress?.phase !== "failed")
|
|
152
|
+
return null;
|
|
153
|
+
for (const step of progress.steps.values()) {
|
|
154
|
+
if (step.lastErrorMessage !== undefined)
|
|
155
|
+
return classifyRunError(step.lastErrorMessage).userMessage;
|
|
117
156
|
}
|
|
118
157
|
return null;
|
|
119
158
|
}
|
|
@@ -124,12 +163,15 @@ export function buildStepperSteps(progress, steps) {
|
|
|
124
163
|
* (a legacy run, or a read error) that can synthesize the same empty-step
|
|
125
164
|
* shape regardless of phase — in that case whether it started is unknown, so
|
|
126
165
|
* this returns `false` rather than making a false "never started" claim.
|
|
127
|
-
*/
|
|
166
|
+
*/
|
|
167
|
+
export function runNeverStarted(progress, logRead) {
|
|
128
168
|
return progress !== null && logRead && progress.steps.size === 0;
|
|
129
169
|
}
|
|
130
170
|
/** Honest copy for the pre-interactive window before a run's first step has
|
|
131
171
|
* recorded any activity: queued (`pending`, or no progress yet) reads
|
|
132
|
-
* differently from a runtime already booting that first step. */
|
|
133
|
-
|
|
172
|
+
* differently from a runtime already booting that first step. */
|
|
173
|
+
export function runStartLabel(progress) {
|
|
174
|
+
if (progress === null || progress.phase === "pending")
|
|
175
|
+
return "Starting your workflow…";
|
|
134
176
|
return "Preparing your workflow…";
|
|
135
177
|
}
|
package/dist/lib/workflow-run.js
CHANGED
|
@@ -4,35 +4,39 @@ export const RUN_STATUS_LABEL = {
|
|
|
4
4
|
awaiting: "Needs you",
|
|
5
5
|
completed: "Completed",
|
|
6
6
|
failed: "Failed",
|
|
7
|
-
stopped: "Stopped"
|
|
7
|
+
stopped: "Stopped",
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
10
|
* State is the only colour on these surfaces. `awaiting` is the one orange:
|
|
11
11
|
* it is the single status a person can act on, and orange is the registry's
|
|
12
12
|
* "this is for you" tone.
|
|
13
|
-
*/
|
|
13
|
+
*/
|
|
14
|
+
export const RUN_STATUS_TONE = {
|
|
14
15
|
provisioning: "neutral",
|
|
15
16
|
running: "info",
|
|
16
17
|
awaiting: "accent",
|
|
17
18
|
completed: "success",
|
|
18
19
|
failed: "danger",
|
|
19
|
-
stopped: "neutral"
|
|
20
|
+
stopped: "neutral",
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
22
23
|
* The dot only marks liveness and failure — the badge above carries the full
|
|
23
24
|
* status colour. See `status-dot.tsx` for why it is three tones and not six.
|
|
24
|
-
*/
|
|
25
|
+
*/
|
|
26
|
+
export const RUN_STATUS_DOT_TONE = {
|
|
25
27
|
provisioning: "neutral",
|
|
26
28
|
running: "emphasis",
|
|
27
29
|
awaiting: "emphasis",
|
|
28
30
|
completed: "neutral",
|
|
29
31
|
failed: "danger",
|
|
30
|
-
stopped: "neutral"
|
|
32
|
+
stopped: "neutral",
|
|
31
33
|
};
|
|
32
|
-
/** A run that will not change again without someone starting a new one. */
|
|
34
|
+
/** A run that will not change again without someone starting a new one. */
|
|
35
|
+
export function isTerminalRun(status) {
|
|
33
36
|
return status === "completed" || status === "failed" || status === "stopped";
|
|
34
37
|
}
|
|
35
|
-
/** Still moving or still asking — what an "active runs" strip shows. */
|
|
38
|
+
/** Still moving or still asking — what an "active runs" strip shows. */
|
|
39
|
+
export function isActiveRun(status) {
|
|
36
40
|
return !isTerminalRun(status);
|
|
37
41
|
}
|
|
38
42
|
// Needs-you first, then in-flight, then whatever finished. Failed sorts above
|
|
@@ -43,14 +47,14 @@ const ATTENTION_RANK = {
|
|
|
43
47
|
provisioning: 1,
|
|
44
48
|
failed: 2,
|
|
45
49
|
completed: 3,
|
|
46
|
-
stopped: 4
|
|
50
|
+
stopped: 4,
|
|
47
51
|
};
|
|
48
|
-
/** Attention order, newest first within a band. Sorts a copy. */
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
].sort((a, b)=>{
|
|
52
|
+
/** Attention order, newest first within a band. Sorts a copy. */
|
|
53
|
+
export function sortRunsByAttention(runs) {
|
|
54
|
+
return [...runs].sort((a, b) => {
|
|
52
55
|
const byAttention = ATTENTION_RANK[a.status] - ATTENTION_RANK[b.status];
|
|
53
|
-
if (byAttention !== 0)
|
|
56
|
+
if (byAttention !== 0)
|
|
57
|
+
return byAttention;
|
|
54
58
|
return Date.parse(b.startedAt) - Date.parse(a.startedAt);
|
|
55
59
|
});
|
|
56
60
|
}
|