oh-my-opencode-dashboard 0.1.0 → 0.1.1

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/src/App.tsx CHANGED
@@ -63,6 +63,266 @@ type TimeSeries = {
63
63
  series: TimeSeriesSeries[];
64
64
  };
65
65
 
66
+ function toNonNegativeFinite(value: unknown): number {
67
+ if (typeof value !== "number") return 0;
68
+ if (!Number.isFinite(value)) return 0;
69
+ return Math.max(0, value);
70
+ }
71
+
72
+ export function computeOtherMainAgentsCount(params: {
73
+ overall: unknown;
74
+ background: unknown;
75
+ sisyphus: unknown;
76
+ prometheus: unknown;
77
+ atlas: unknown;
78
+ }): number {
79
+ const overall = toNonNegativeFinite(params.overall);
80
+ const background = toNonNegativeFinite(params.background);
81
+ const sisyphus = toNonNegativeFinite(params.sisyphus);
82
+ const prometheus = toNonNegativeFinite(params.prometheus);
83
+ const atlas = toNonNegativeFinite(params.atlas);
84
+
85
+ const mainTotal = Math.max(0, overall - background);
86
+ return Math.max(0, mainTotal - sisyphus - prometheus - atlas);
87
+ }
88
+
89
+ export function computeMainAgentsScaleMax(params: {
90
+ buckets: number;
91
+ overallValues: unknown[];
92
+ backgroundValues: unknown[];
93
+ sisyphusValues: unknown[];
94
+ prometheusValues: unknown[];
95
+ atlasValues: unknown[];
96
+ }): number {
97
+ const buckets = Math.max(0, Math.floor(params.buckets));
98
+ let sumMax = 0;
99
+
100
+ for (let i = 0; i < buckets; i++) {
101
+ const sis = toNonNegativeFinite(params.sisyphusValues[i]);
102
+ const pro = toNonNegativeFinite(params.prometheusValues[i]);
103
+ const atl = toNonNegativeFinite(params.atlasValues[i]);
104
+ const other = computeOtherMainAgentsCount({
105
+ overall: params.overallValues[i],
106
+ background: params.backgroundValues[i],
107
+ sisyphus: sis,
108
+ prometheus: pro,
109
+ atlas: atl,
110
+ });
111
+ const s = sis + pro + atl + other;
112
+ if (s > sumMax) sumMax = s;
113
+ }
114
+
115
+ return Math.max(1, sumMax || 1);
116
+ }
117
+
118
+ export function TimeSeriesActivitySection(props: { timeSeries: TimeSeries }) {
119
+ const timeSeriesById = new Map<TimeSeriesSeriesId, TimeSeriesSeries>();
120
+ for (const s of props.timeSeries.series) {
121
+ if (s && typeof s.id === "string") {
122
+ timeSeriesById.set(s.id, s);
123
+ }
124
+ }
125
+
126
+ const buckets = Math.max(1, props.timeSeries.buckets);
127
+ const bucketMs = Math.max(1, props.timeSeries.bucketMs);
128
+ const viewBox = `0 0 ${buckets} 28`;
129
+ const minuteStep = Math.max(1, Math.round(60_000 / bucketMs));
130
+ const bucketStartMs = props.timeSeries.anchorMs - (buckets - 1) * bucketMs;
131
+
132
+ const overallValues = timeSeriesById.get("overall-main")?.values ?? [];
133
+
134
+ return (
135
+ <section className="timeSeries">
136
+ <div className="timeSeriesHeader">
137
+ <h2 className="timeSeriesTitle">Time-series activity</h2>
138
+ <p className="timeSeriesSub">Last 5 minutes</p>
139
+ </div>
140
+
141
+ <div className="timeSeriesRows">
142
+ {(
143
+ [
144
+ {
145
+ kind: "main-agents" as const,
146
+ label: "Main agents" as const,
147
+ },
148
+ {
149
+ kind: "single" as const,
150
+ label: "background tasks (total)",
151
+ tone: "muted" as const,
152
+ overlayId: "background-total" as const,
153
+ baseline: false,
154
+ },
155
+ ] as const
156
+ ).map((row) => {
157
+ const H = 28;
158
+ const padTop = 2;
159
+ const padBottom = 2;
160
+ const chartHeight = H - padTop - padBottom;
161
+ const baselineY = H - padBottom;
162
+ const barW = 0.85;
163
+ const barInset = (1 - barW) / 2;
164
+
165
+ if (row.kind === "main-agents") {
166
+ const sisyphusValues = timeSeriesById.get("agent:sisyphus")?.values ?? [];
167
+ const prometheusValues = timeSeriesById.get("agent:prometheus")?.values ?? [];
168
+ const atlasValues = timeSeriesById.get("agent:atlas")?.values ?? [];
169
+ const backgroundValues = timeSeriesById.get("background-total")?.values ?? [];
170
+
171
+ const scaleMax = computeMainAgentsScaleMax({
172
+ buckets,
173
+ overallValues,
174
+ backgroundValues,
175
+ sisyphusValues,
176
+ prometheusValues,
177
+ atlasValues,
178
+ });
179
+
180
+ return (
181
+ <div key="main-agents" className="timeSeriesRow">
182
+ <div className="timeSeriesRowLabel">{row.label}</div>
183
+ <div className="timeSeriesSvgWrap">
184
+ <svg className="timeSeriesSvg" viewBox={viewBox} preserveAspectRatio="none" aria-hidden="true">
185
+ {Array.from({ length: Math.floor(buckets / minuteStep) + 1 }, (_, idx) => {
186
+ const x = idx * minuteStep;
187
+ if (x < 0 || x > buckets) return null;
188
+ return (
189
+ <line
190
+ key={`g-${bucketStartMs + x * bucketMs}`}
191
+ className="timeSeriesGridline"
192
+ x1={x}
193
+ x2={x}
194
+ y1={0}
195
+ y2={H}
196
+ />
197
+ );
198
+ })}
199
+
200
+ {Array.from({ length: buckets }, (_, i) => {
201
+ const bucketMsAt = bucketStartMs + i * bucketMs;
202
+ const barX = i + barInset;
203
+
204
+ const sis = toNonNegativeFinite(sisyphusValues[i]);
205
+ const pro = toNonNegativeFinite(prometheusValues[i]);
206
+ const atl = toNonNegativeFinite(atlasValues[i]);
207
+ const other = computeOtherMainAgentsCount({
208
+ overall: overallValues[i],
209
+ background: backgroundValues[i],
210
+ sisyphus: sis,
211
+ prometheus: pro,
212
+ atlas: atl,
213
+ });
214
+
215
+ const segments = computeStackedSegments(
216
+ {
217
+ sisyphus: sis,
218
+ prometheus: pro,
219
+ atlas: atl,
220
+ other,
221
+ },
222
+ scaleMax,
223
+ chartHeight
224
+ );
225
+
226
+ if (segments.length === 0) return null;
227
+ return segments.map((seg) => (
228
+ <rect
229
+ key={`main-agents-${bucketMsAt}-${seg.tone}`}
230
+ className={`timeSeriesBar timeSeriesBar--${seg.tone}`}
231
+ x={barX}
232
+ y={padTop + seg.y}
233
+ width={barW}
234
+ height={seg.height}
235
+ />
236
+ ));
237
+ })}
238
+ </svg>
239
+ </div>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ const overlayValues = timeSeriesById.get(row.overlayId)?.values ?? [];
245
+ const baselineMax = row.baseline ? maxCount(overallValues) : 0;
246
+ const overlayMax = maxCount(overlayValues);
247
+ const scaleMax = Math.max(1, row.baseline ? Math.max(baselineMax, overlayMax) : overlayMax || 1);
248
+
249
+ return (
250
+ <div key={row.overlayId} className="timeSeriesRow" data-tone={row.tone}>
251
+ <div className="timeSeriesRowLabel">{row.label}</div>
252
+ <div className="timeSeriesSvgWrap">
253
+ <svg className="timeSeriesSvg" viewBox={viewBox} preserveAspectRatio="none" aria-hidden="true">
254
+ {Array.from({ length: Math.floor(buckets / minuteStep) + 1 }, (_, idx) => {
255
+ const x = idx * minuteStep;
256
+ if (x < 0 || x > buckets) return null;
257
+ return (
258
+ <line
259
+ key={`g-${bucketStartMs + x * bucketMs}`}
260
+ className="timeSeriesGridline"
261
+ x1={x}
262
+ x2={x}
263
+ y1={0}
264
+ y2={H}
265
+ />
266
+ );
267
+ })}
268
+
269
+ {row.baseline
270
+ ? overallValues.slice(0, buckets).map((v, i) => {
271
+ const h = barHeight(v ?? 0, scaleMax, chartHeight);
272
+ if (!h) return null;
273
+ const barX = i + barInset;
274
+ const bucketMsAt = bucketStartMs + i * bucketMs;
275
+ return (
276
+ <rect
277
+ key={`b-${bucketMsAt}`}
278
+ className="timeSeriesBarBaseline"
279
+ x={barX}
280
+ y={baselineY - h}
281
+ width={barW}
282
+ height={h}
283
+ />
284
+ );
285
+ })
286
+ : null}
287
+
288
+ {overlayValues.slice(0, buckets).map((v, i) => {
289
+ const h = barHeight(v ?? 0, scaleMax, chartHeight);
290
+ if (!h) return null;
291
+ const barX = i + barInset;
292
+ const bucketMsAt = bucketStartMs + i * bucketMs;
293
+ return (
294
+ <rect
295
+ key={`${row.overlayId}-${bucketMsAt}`}
296
+ className="timeSeriesBar"
297
+ x={barX}
298
+ y={baselineY - h}
299
+ width={barW}
300
+ height={h}
301
+ />
302
+ );
303
+ })}
304
+ </svg>
305
+ </div>
306
+ </div>
307
+ );
308
+ })}
309
+ </div>
310
+
311
+ <div className="timeSeriesAxisBottom" aria-hidden="true">
312
+ <div />
313
+ <div className="timeSeriesAxisBottomLabels">
314
+ <span className="timeSeriesAxisBottomLabel">-5m</span>
315
+ <span className="timeSeriesAxisBottomLabel">-4m</span>
316
+ <span className="timeSeriesAxisBottomLabel">-3m</span>
317
+ <span className="timeSeriesAxisBottomLabel">-2m</span>
318
+ <span className="timeSeriesAxisBottomLabel">-1m</span>
319
+ <span className="timeSeriesAxisBottomLabel">Now</span>
320
+ </div>
321
+ </div>
322
+ </section>
323
+ );
324
+ }
325
+
66
326
  type DashboardPayload = {
67
327
  mainSession: {
68
328
  agent: string;
@@ -732,15 +992,6 @@ export default function App() {
732
992
  const liveLabel = connected ? "Live" : "Disconnected";
733
993
  const liveTone = connected ? "teal" : "sand";
734
994
 
735
- const timeSeriesById = React.useMemo(() => {
736
- const map = new Map<TimeSeriesSeriesId, TimeSeriesSeries>();
737
- for (const s of data.timeSeries.series) {
738
- if (s && typeof s.id === "string") {
739
- map.set(s.id, s);
740
- }
741
- }
742
- return map;
743
- }, [data.timeSeries.series]);
744
995
 
745
996
  const fetchToolCalls = React.useCallback(async (sessionId: string, opts: { force: boolean }) => {
746
997
  const existing = toolCallsBySessionRef.current.get(sessionId);
@@ -871,14 +1122,6 @@ export default function App() {
871
1122
  }
872
1123
  }, [connected, data.backgroundTasks, data.mainSessionTasks, expandedBgTaskIds, expandedMainTaskIds, fetchToolCalls]);
873
1124
 
874
- const buckets = Math.max(1, data.timeSeries.buckets);
875
- const bucketMs = Math.max(1, data.timeSeries.bucketMs);
876
- const viewBox = `0 0 ${buckets} 28`;
877
- const minuteStep = Math.max(1, Math.round(60_000 / bucketMs));
878
- const bucketStartMs = data.timeSeries.anchorMs - (buckets - 1) * bucketMs;
879
-
880
- const overallValues = timeSeriesById.get("overall-main")?.values ?? [];
881
-
882
1125
  return (
883
1126
  <div className="page">
884
1127
  <div className="container">
@@ -923,201 +1166,7 @@ export default function App() {
923
1166
  </header>
924
1167
 
925
1168
  <main className="stack">
926
- <section className="timeSeries">
927
- <div className="timeSeriesHeader">
928
- <h2 className="timeSeriesTitle">Time-series activity</h2>
929
- <p className="timeSeriesSub">Last 5 minutes</p>
930
- </div>
931
-
932
- <div className="timeSeriesAxisTop" aria-hidden="true">
933
- <div />
934
- <div className="timeSeriesAxisTopLabels">
935
- <span className="timeSeriesAxisTopLabel">-5m</span>
936
- <span className="timeSeriesAxisTopLabel">-4m</span>
937
- <span className="timeSeriesAxisTopLabel">-3m</span>
938
- <span className="timeSeriesAxisTopLabel">-1m</span>
939
- </div>
940
- </div>
941
-
942
- <div className="timeSeriesRows">
943
- {(
944
- [
945
- {
946
- kind: "main-agents" as const,
947
- label: "Main agents" as const,
948
- },
949
- {
950
- kind: "single" as const,
951
- label: "background tasks (total)",
952
- tone: "muted" as const,
953
- overlayId: "background-total" as const,
954
- baseline: false,
955
- },
956
- ] as const
957
- ).map((row) => {
958
- const H = 28;
959
- const padTop = 2;
960
- const padBottom = 2;
961
- const chartHeight = H - padTop - padBottom;
962
- const baselineY = H - padBottom;
963
- const barW = 0.85;
964
- const barInset = (1 - barW) / 2;
965
-
966
- if (row.kind === "main-agents") {
967
- const sisyphusValues = timeSeriesById.get("agent:sisyphus")?.values ?? [];
968
- const prometheusValues = timeSeriesById.get("agent:prometheus")?.values ?? [];
969
- const atlasValues = timeSeriesById.get("agent:atlas")?.values ?? [];
970
-
971
- let sumMax = 0;
972
- for (let i = 0; i < buckets; i++) {
973
- const rawSis = sisyphusValues[i];
974
- const rawPro = prometheusValues[i];
975
- const rawAtl = atlasValues[i];
976
- const sis = typeof rawSis === "number" && Number.isFinite(rawSis) ? Math.max(0, rawSis) : 0;
977
- const pro = typeof rawPro === "number" && Number.isFinite(rawPro) ? Math.max(0, rawPro) : 0;
978
- const atl = typeof rawAtl === "number" && Number.isFinite(rawAtl) ? Math.max(0, rawAtl) : 0;
979
- const s = sis + pro + atl;
980
- if (s > sumMax) sumMax = s;
981
- }
982
-
983
- const scaleMax = Math.max(1, sumMax || 1);
984
-
985
- return (
986
- <div key="main-agents" className="timeSeriesRow">
987
- <div className="timeSeriesRowLabel">{row.label}</div>
988
- <div className="timeSeriesSvgWrap">
989
- <svg className="timeSeriesSvg" viewBox={viewBox} preserveAspectRatio="none" aria-hidden="true">
990
- {Array.from({ length: Math.floor(buckets / minuteStep) + 1 }, (_, idx) => {
991
- const x = idx * minuteStep;
992
- if (x < 0 || x > buckets) return null;
993
- return (
994
- <line
995
- key={`g-${bucketStartMs + x * bucketMs}`}
996
- className="timeSeriesGridline"
997
- x1={x}
998
- x2={x}
999
- y1={0}
1000
- y2={H}
1001
- />
1002
- );
1003
- })}
1004
-
1005
- {Array.from({ length: buckets }, (_, i) => {
1006
- const bucketMsAt = bucketStartMs + i * bucketMs;
1007
- const barX = i + barInset;
1008
- const rawSis = sisyphusValues[i];
1009
- const rawPro = prometheusValues[i];
1010
- const rawAtl = atlasValues[i];
1011
- const sis = typeof rawSis === "number" && Number.isFinite(rawSis) ? Math.max(0, rawSis) : 0;
1012
- const pro = typeof rawPro === "number" && Number.isFinite(rawPro) ? Math.max(0, rawPro) : 0;
1013
- const atl = typeof rawAtl === "number" && Number.isFinite(rawAtl) ? Math.max(0, rawAtl) : 0;
1014
- const segments = computeStackedSegments(
1015
- {
1016
- sisyphus: sis,
1017
- prometheus: pro,
1018
- atlas: atl,
1019
- },
1020
- scaleMax,
1021
- chartHeight
1022
- );
1023
-
1024
- if (segments.length === 0) return null;
1025
- return segments.map((seg) => (
1026
- <rect
1027
- key={`main-agents-${bucketMsAt}-${seg.tone}`}
1028
- className={`timeSeriesBar timeSeriesBar--${seg.tone}`}
1029
- x={barX}
1030
- y={padTop + seg.y}
1031
- width={barW}
1032
- height={seg.height}
1033
- />
1034
- ));
1035
- })}
1036
- </svg>
1037
- </div>
1038
- </div>
1039
- );
1040
- }
1041
-
1042
- const overlayValues = timeSeriesById.get(row.overlayId)?.values ?? [];
1043
- const baselineMax = row.baseline ? maxCount(overallValues) : 0;
1044
- const overlayMax = maxCount(overlayValues);
1045
- const scaleMax = Math.max(1, row.baseline ? Math.max(baselineMax, overlayMax) : overlayMax || 1);
1046
-
1047
- return (
1048
- <div key={row.overlayId} className="timeSeriesRow" data-tone={row.tone}>
1049
- <div className="timeSeriesRowLabel">{row.label}</div>
1050
- <div className="timeSeriesSvgWrap">
1051
- <svg className="timeSeriesSvg" viewBox={viewBox} preserveAspectRatio="none" aria-hidden="true">
1052
- {Array.from({ length: Math.floor(buckets / minuteStep) + 1 }, (_, idx) => {
1053
- const x = idx * minuteStep;
1054
- if (x < 0 || x > buckets) return null;
1055
- return (
1056
- <line
1057
- key={`g-${bucketStartMs + x * bucketMs}`}
1058
- className="timeSeriesGridline"
1059
- x1={x}
1060
- x2={x}
1061
- y1={0}
1062
- y2={H}
1063
- />
1064
- );
1065
- })}
1066
-
1067
- {row.baseline
1068
- ? overallValues.slice(0, buckets).map((v, i) => {
1069
- const h = barHeight(v ?? 0, scaleMax, chartHeight);
1070
- if (!h) return null;
1071
- const barX = i + barInset;
1072
- const bucketMsAt = bucketStartMs + i * bucketMs;
1073
- return (
1074
- <rect
1075
- key={`b-${bucketMsAt}`}
1076
- className="timeSeriesBarBaseline"
1077
- x={barX}
1078
- y={baselineY - h}
1079
- width={barW}
1080
- height={h}
1081
- />
1082
- );
1083
- })
1084
- : null}
1085
-
1086
- {overlayValues.slice(0, buckets).map((v, i) => {
1087
- const h = barHeight(v ?? 0, scaleMax, chartHeight);
1088
- if (!h) return null;
1089
- const barX = i + barInset;
1090
- const bucketMsAt = bucketStartMs + i * bucketMs;
1091
- return (
1092
- <rect
1093
- key={`${row.overlayId}-${bucketMsAt}`}
1094
- className="timeSeriesBar"
1095
- x={barX}
1096
- y={baselineY - h}
1097
- width={barW}
1098
- height={h}
1099
- />
1100
- );
1101
- })}
1102
- </svg>
1103
- </div>
1104
- </div>
1105
- );
1106
- })}
1107
- </div>
1108
-
1109
- <div className="timeSeriesAxisBottom" aria-hidden="true">
1110
- <div />
1111
- <div className="timeSeriesAxisBottomLabels">
1112
- <span className="timeSeriesAxisBottomLabel">-5m</span>
1113
- <span className="timeSeriesAxisBottomLabel">-4m</span>
1114
- <span className="timeSeriesAxisBottomLabel">-3m</span>
1115
- <span className="timeSeriesAxisBottomLabel">-2m</span>
1116
- <span className="timeSeriesAxisBottomLabel">-1m</span>
1117
- <span className="timeSeriesAxisBottomLabel">Now</span>
1118
- </div>
1119
- </div>
1120
- </section>
1169
+ <TimeSeriesActivitySection timeSeries={data.timeSeries} />
1121
1170
 
1122
1171
  <section className="grid2">
1123
1172
  <article className="card">
@@ -79,7 +79,15 @@ function hasSensitiveKeys(value: unknown): boolean {
79
79
 
80
80
  const createStore = (): DashboardStore => ({
81
81
  getSnapshot: (): DashboardPayload => ({
82
- mainSession: { agent: "x", currentModel: null, currentTool: "-", lastUpdatedLabel: "never", session: "s", statusPill: "idle" },
82
+ mainSession: {
83
+ agent: "x",
84
+ currentModel: null,
85
+ currentTool: "-",
86
+ lastUpdatedLabel: "never",
87
+ session: "s",
88
+ sessionId: null,
89
+ statusPill: "idle",
90
+ },
83
91
  planProgress: { name: "p", completed: 0, total: 0, path: "", statusPill: "not started", steps: [] as PlanStep[] },
84
92
  backgroundTasks: [],
85
93
  mainSessionTasks: [],
@@ -0,0 +1,111 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import * as React from "react";
3
+ import { renderToStaticMarkup } from "react-dom/server";
4
+
5
+ import {
6
+ TimeSeriesActivitySection,
7
+ computeMainAgentsScaleMax,
8
+ computeOtherMainAgentsCount,
9
+ } from "./App";
10
+
11
+ type TimeSeriesProps = React.ComponentProps<typeof TimeSeriesActivitySection>;
12
+
13
+ function mkTimeSeries(override?: Partial<TimeSeriesProps["timeSeries"]>): TimeSeriesProps["timeSeries"] {
14
+ const buckets = 3;
15
+ const mkSeries = (id: TimeSeriesProps["timeSeries"]["series"][number]["id"], values: number[]) => ({
16
+ id,
17
+ label: id,
18
+ tone: "muted" as const,
19
+ values,
20
+ });
21
+
22
+ return {
23
+ windowMs: 300_000,
24
+ buckets,
25
+ bucketMs: 2_000,
26
+ anchorMs: 0,
27
+ serverNowMs: 0,
28
+ series: [
29
+ mkSeries("overall-main", [0, 0, 0]),
30
+ mkSeries("agent:sisyphus", [0, 0, 0]),
31
+ mkSeries("agent:prometheus", [0, 0, 0]),
32
+ mkSeries("agent:atlas", [0, 0, 0]),
33
+ mkSeries("background-total", [0, 0, 0]),
34
+ ],
35
+ ...override,
36
+ };
37
+ }
38
+
39
+ describe("TimeSeriesActivitySection (SSR)", () => {
40
+ it("should not render top axis and should keep bottom axis", () => {
41
+ // #given
42
+ const timeSeries = mkTimeSeries();
43
+
44
+ // #when
45
+ const html = renderToStaticMarkup(<TimeSeriesActivitySection timeSeries={timeSeries} />);
46
+
47
+ // #then
48
+ expect(html).not.toContain("timeSeriesAxisTop");
49
+ expect(html).toContain("timeSeriesAxisBottom");
50
+ });
51
+
52
+ it("should render sand bars for other main agents when derived otherMain is non-zero", () => {
53
+ // #given
54
+ const timeSeries = mkTimeSeries({
55
+ series: [
56
+ { id: "overall-main", label: "Overall", tone: "muted", values: [10, 0, 0] },
57
+ { id: "agent:sisyphus", label: "Sisyphus", tone: "teal", values: [0, 0, 0] },
58
+ { id: "agent:prometheus", label: "Prometheus", tone: "red", values: [0, 0, 0] },
59
+ { id: "agent:atlas", label: "Atlas", tone: "green", values: [0, 0, 0] },
60
+ { id: "background-total", label: "Background", tone: "muted", values: [0, 0, 0] },
61
+ ],
62
+ });
63
+
64
+ // #when
65
+ const html = renderToStaticMarkup(<TimeSeriesActivitySection timeSeries={timeSeries} />);
66
+
67
+ // #then
68
+ expect(html).toContain("timeSeriesBar--sand");
69
+ });
70
+ });
71
+
72
+ describe("time-series helpers", () => {
73
+ it("computeOtherMainAgentsCount should clamp to >= 0 and ignore invalid numbers", () => {
74
+ // #given
75
+ const value = computeOtherMainAgentsCount({
76
+ overall: 10,
77
+ background: 3,
78
+ sisyphus: 2,
79
+ prometheus: 1,
80
+ atlas: 0,
81
+ });
82
+
83
+ // #then
84
+ expect(value).toBe(4);
85
+
86
+ expect(
87
+ computeOtherMainAgentsCount({
88
+ overall: NaN,
89
+ background: Infinity,
90
+ sisyphus: -1,
91
+ prometheus: 0,
92
+ atlas: 0,
93
+ })
94
+ ).toBe(0);
95
+ });
96
+
97
+ it("computeMainAgentsScaleMax should include otherMain in the max", () => {
98
+ // #given
99
+ const scaleMax = computeMainAgentsScaleMax({
100
+ buckets: 1,
101
+ overallValues: [10],
102
+ backgroundValues: [0],
103
+ sisyphusValues: [0],
104
+ prometheusValues: [0],
105
+ atlasValues: [0],
106
+ });
107
+
108
+ // #then
109
+ expect(scaleMax).toBe(10);
110
+ });
111
+ });