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.
@@ -4,21 +4,21 @@ import { computeStackedSegments, AgentCounts, StackedSegment } from "./timeserie
4
4
  describe("computeStackedSegments", () => {
5
5
  describe("Edge cases", () => {
6
6
  it("should return empty array when chartHeight <= 0", () => {
7
- const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3 };
7
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3, other: 0 };
8
8
 
9
9
  expect(computeStackedSegments(counts, 20, 0)).toEqual([]);
10
10
  expect(computeStackedSegments(counts, 20, -5)).toEqual([]);
11
11
  });
12
12
 
13
13
  it("should return empty array when scaleMax <= 0", () => {
14
- const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3 };
14
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3, other: 0 };
15
15
 
16
16
  expect(computeStackedSegments(counts, 0, 100)).toEqual([]);
17
17
  expect(computeStackedSegments(counts, -10, 100)).toEqual([]);
18
18
  });
19
19
 
20
20
  it("should return empty array when all counts are zero", () => {
21
- const counts: AgentCounts = { sisyphus: 0, prometheus: 0, atlas: 0 };
21
+ const counts: AgentCounts = { sisyphus: 0, prometheus: 0, atlas: 0, other: 0 };
22
22
 
23
23
  const result = computeStackedSegments(counts, 20, 100);
24
24
  expect(result).toEqual([]);
@@ -29,6 +29,7 @@ describe("computeStackedSegments", () => {
29
29
  sisyphus: NaN,
30
30
  prometheus: Infinity,
31
31
  atlas: -5,
32
+ other: NaN,
32
33
  } as unknown as AgentCounts;
33
34
 
34
35
  const result = computeStackedSegments(invalidCounts, 20, 100);
@@ -40,6 +41,7 @@ describe("computeStackedSegments", () => {
40
41
  sisyphus: 10,
41
42
  prometheus: NaN,
42
43
  atlas: -3,
44
+ other: Infinity,
43
45
  } as unknown as AgentCounts;
44
46
 
45
47
  const result = computeStackedSegments(mixedCounts, 20, 100);
@@ -54,7 +56,7 @@ describe("computeStackedSegments", () => {
54
56
 
55
57
  describe("Single agent scenarios", () => {
56
58
  it("should return one segment when only sisyphus is non-zero", () => {
57
- const counts: AgentCounts = { sisyphus: 10, prometheus: 0, atlas: 0 };
59
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 0, atlas: 0, other: 0 };
58
60
 
59
61
  const result = computeStackedSegments(counts, 20, 100);
60
62
  expect(result).toHaveLength(1);
@@ -66,7 +68,7 @@ describe("computeStackedSegments", () => {
66
68
  });
67
69
 
68
70
  it("should return one segment when only prometheus is non-zero", () => {
69
- const counts: AgentCounts = { sisyphus: 0, prometheus: 15, atlas: 0 };
71
+ const counts: AgentCounts = { sisyphus: 0, prometheus: 15, atlas: 0, other: 0 };
70
72
 
71
73
  const result = computeStackedSegments(counts, 30, 120);
72
74
  expect(result).toHaveLength(1);
@@ -78,7 +80,7 @@ describe("computeStackedSegments", () => {
78
80
  });
79
81
 
80
82
  it("should return one segment when only atlas is non-zero", () => {
81
- const counts: AgentCounts = { sisyphus: 0, prometheus: 0, atlas: 8 };
83
+ const counts: AgentCounts = { sisyphus: 0, prometheus: 0, atlas: 8, other: 0 };
82
84
 
83
85
  const result = computeStackedSegments(counts, 16, 80);
84
86
  expect(result).toHaveLength(1);
@@ -90,7 +92,7 @@ describe("computeStackedSegments", () => {
90
92
  });
91
93
 
92
94
  it("should round to at least 1px for non-zero values", () => {
93
- const counts: AgentCounts = { sisyphus: 1, prometheus: 0, atlas: 0 };
95
+ const counts: AgentCounts = { sisyphus: 1, prometheus: 0, atlas: 0, other: 0 };
94
96
 
95
97
  const result = computeStackedSegments(counts, 1000, 100);
96
98
  expect(result).toHaveLength(1);
@@ -100,7 +102,7 @@ describe("computeStackedSegments", () => {
100
102
 
101
103
  describe("Multiple agent scenarios", () => {
102
104
  it("should return multiple segments in correct order (bottom to top)", () => {
103
- const counts: AgentCounts = { sisyphus: 10, prometheus: 20, atlas: 15 };
105
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 20, atlas: 15, other: 0 };
104
106
 
105
107
  const result = computeStackedSegments(counts, 50, 100);
106
108
  expect(result).toHaveLength(3);
@@ -116,7 +118,7 @@ describe("computeStackedSegments", () => {
116
118
  });
117
119
 
118
120
  it("should correctly calculate heights for all agents", () => {
119
- const counts: AgentCounts = { sisyphus: 10, prometheus: 20, atlas: 15 };
121
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 20, atlas: 15, other: 0 };
120
122
 
121
123
  const result = computeStackedSegments(counts, 50, 100);
122
124
  const totalHeight = result.reduce((sum, seg) => sum + seg.height, 0);
@@ -130,7 +132,7 @@ describe("computeStackedSegments", () => {
130
132
  });
131
133
 
132
134
  it("should handle zero values mixed with non-zero values", () => {
133
- const counts: AgentCounts = { sisyphus: 10, prometheus: 0, atlas: 15 };
135
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 0, atlas: 15, other: 0 };
134
136
 
135
137
  const result = computeStackedSegments(counts, 30, 90);
136
138
  expect(result).toHaveLength(2);
@@ -142,11 +144,21 @@ describe("computeStackedSegments", () => {
142
144
  // Check positioning
143
145
  expect(result[0].y).toBeGreaterThan(result[1].y);
144
146
  });
147
+ it("should include sand segment when other is non-zero", () => {
148
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 20, atlas: 15, other: 5 };
149
+ const result = computeStackedSegments(counts, 50, 100);
150
+ expect(result).toHaveLength(4);
151
+
152
+ expect(result[0].tone).toBe("teal");
153
+ expect(result[1].tone).toBe("red");
154
+ expect(result[2].tone).toBe("green");
155
+ expect(result[3].tone).toBe("sand");
156
+ });
145
157
  });
146
158
 
147
159
  describe("Clamping and overflow behavior", () => {
148
160
  it("should ensure sum of heights never exceeds chartHeight", () => {
149
- const counts: AgentCounts = { sisyphus: 100, prometheus: 100, atlas: 100 };
161
+ const counts: AgentCounts = { sisyphus: 100, prometheus: 100, atlas: 100, other: 100 };
150
162
 
151
163
  const result = computeStackedSegments(counts, 100, 50); // Should overflow
152
164
  const totalHeight = result.reduce((sum, seg) => sum + seg.height, 0);
@@ -155,17 +167,17 @@ describe("computeStackedSegments", () => {
155
167
  });
156
168
 
157
169
  it("should preserve at least 1px for non-zero agents when possible", () => {
158
- const counts: AgentCounts = { sisyphus: 1, prometheus: 1, atlas: 1 };
170
+ const counts: AgentCounts = { sisyphus: 1, prometheus: 1, atlas: 1, other: 1 };
159
171
 
160
172
  const result = computeStackedSegments(counts, 100, 10);
161
173
 
162
174
  // All agents should be visible with at least 1px each
163
- expect(result).toHaveLength(3);
175
+ expect(result).toHaveLength(4);
164
176
  expect(result.every(seg => seg.height >= 1)).toBe(true);
165
177
  });
166
178
 
167
179
  it("should distribute overflow reduction fairly", () => {
168
- const counts: AgentCounts = { sisyphus: 40, prometheus: 35, atlas: 25 };
180
+ const counts: AgentCounts = { sisyphus: 40, prometheus: 35, atlas: 25, other: 10 };
169
181
 
170
182
  const result = computeStackedSegments(counts, 100, 80);
171
183
  const totalHeight = result.reduce((sum, seg) => sum + seg.height, 0);
@@ -178,7 +190,7 @@ describe("computeStackedSegments", () => {
178
190
  });
179
191
 
180
192
  it("should handle extreme overflow gracefully", () => {
181
- const counts: AgentCounts = { sisyphus: 1000, prometheus: 1000, atlas: 1000 };
193
+ const counts: AgentCounts = { sisyphus: 1000, prometheus: 1000, atlas: 1000, other: 1000 };
182
194
 
183
195
  const result = computeStackedSegments(counts, 100, 5);
184
196
  const totalHeight = result.reduce((sum, seg) => sum + seg.height, 0);
@@ -191,7 +203,7 @@ describe("computeStackedSegments", () => {
191
203
 
192
204
  describe("Deterministic behavior", () => {
193
205
  it("should produce identical results for identical inputs", () => {
194
- const counts: AgentCounts = { sisyphus: 15, prometheus: 25, atlas: 10 };
206
+ const counts: AgentCounts = { sisyphus: 15, prometheus: 25, atlas: 10, other: 0 };
195
207
 
196
208
  const result1 = computeStackedSegments(counts, 60, 100);
197
209
  const result2 = computeStackedSegments(counts, 60, 100);
@@ -201,10 +213,10 @@ describe("computeStackedSegments", () => {
201
213
 
202
214
  it("should maintain consistent segment order regardless of input magnitudes", () => {
203
215
  const testCases = [
204
- { sisyphus: 100, prometheus: 1, atlas: 1 },
205
- { sisyphus: 1, prometheus: 100, atlas: 1 },
206
- { sisyphus: 1, prometheus: 1, atlas: 100 },
207
- { sisyphus: 50, prometheus: 25, atlas: 75 },
216
+ { sisyphus: 100, prometheus: 1, atlas: 1, other: 0 },
217
+ { sisyphus: 1, prometheus: 100, atlas: 1, other: 0 },
218
+ { sisyphus: 1, prometheus: 1, atlas: 100, other: 0 },
219
+ { sisyphus: 50, prometheus: 25, atlas: 75, other: 0 },
208
220
  ] as AgentCounts[];
209
221
 
210
222
  testCases.forEach(counts => {
@@ -230,7 +242,7 @@ describe("computeStackedSegments", () => {
230
242
 
231
243
  describe("Boundary conditions", () => {
232
244
  it("should handle very small chartHeight", () => {
233
- const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3 };
245
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3, other: 0 };
234
246
 
235
247
  const result = computeStackedSegments(counts, 20, 1);
236
248
  const totalHeight = result.reduce((sum, seg) => sum + seg.height, 0);
@@ -239,7 +251,7 @@ describe("computeStackedSegments", () => {
239
251
  });
240
252
 
241
253
  it("should handle very large scaleMax", () => {
242
- const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3 };
254
+ const counts: AgentCounts = { sisyphus: 10, prometheus: 5, atlas: 3, other: 0 };
243
255
 
244
256
  const result = computeStackedSegments(counts, 1000000, 100);
245
257
 
@@ -249,7 +261,7 @@ describe("computeStackedSegments", () => {
249
261
  });
250
262
 
251
263
  it("should handle fractional results correctly", () => {
252
- const counts: AgentCounts = { sisyphus: 1, prometheus: 1, atlas: 1 };
264
+ const counts: AgentCounts = { sisyphus: 1, prometheus: 1, atlas: 1, other: 1 };
253
265
 
254
266
  const result = computeStackedSegments(counts, 3, 10);
255
267
 
@@ -258,4 +270,4 @@ describe("computeStackedSegments", () => {
258
270
  expect(result.every(seg => Number.isInteger(seg.y))).toBe(true);
259
271
  });
260
272
  });
261
- });
273
+ });
@@ -3,7 +3,7 @@
3
3
  * Independent of React/DOM for testability.
4
4
  */
5
5
 
6
- export type AgentTone = "teal" | "red" | "green";
6
+ export type AgentTone = "teal" | "red" | "green" | "sand";
7
7
 
8
8
  export interface StackedSegment {
9
9
  tone: AgentTone;
@@ -15,6 +15,7 @@ export interface AgentCounts {
15
15
  sisyphus: number;
16
16
  prometheus: number;
17
17
  atlas: number;
18
+ other: number;
18
19
  }
19
20
 
20
21
  /**
@@ -24,6 +25,7 @@ export interface AgentCounts {
24
25
  * 1. Sisyphus (teal) - bottom
25
26
  * 2. Prometheus (red) - middle
26
27
  * 3. Atlas (green) - top
28
+ * 4. Other main agents (sand) - top
27
29
  *
28
30
  * @param counts - Agent counts for this bucket
29
31
  * @param scaleMax - Maximum value for scaling (must be > 0)
@@ -45,9 +47,10 @@ export function computeStackedSegments(
45
47
  sisyphus: Math.max(0, Number.isFinite(counts.sisyphus) ? counts.sisyphus : 0),
46
48
  prometheus: Math.max(0, Number.isFinite(counts.prometheus) ? counts.prometheus : 0),
47
49
  atlas: Math.max(0, Number.isFinite(counts.atlas) ? counts.atlas : 0),
50
+ other: Math.max(0, Number.isFinite(counts.other) ? counts.other : 0),
48
51
  };
49
52
 
50
- const total = sanitized.sisyphus + sanitized.prometheus + sanitized.atlas;
53
+ const total = sanitized.sisyphus + sanitized.prometheus + sanitized.atlas + sanitized.other;
51
54
  if (total === 0) {
52
55
  return [];
53
56
  }
@@ -57,6 +60,7 @@ export function computeStackedSegments(
57
60
  sisyphus: (sanitized.sisyphus / scaleMax) * chartHeight,
58
61
  prometheus: (sanitized.prometheus / scaleMax) * chartHeight,
59
62
  atlas: (sanitized.atlas / scaleMax) * chartHeight,
63
+ other: (sanitized.other / scaleMax) * chartHeight,
60
64
  };
61
65
 
62
66
  // Round to pixels, ensuring sum never exceeds chartHeight
@@ -64,10 +68,11 @@ export function computeStackedSegments(
64
68
  sisyphus: Math.max(1, Math.round(rawHeights.sisyphus)) * (sanitized.sisyphus > 0 ? 1 : 0),
65
69
  prometheus: Math.max(1, Math.round(rawHeights.prometheus)) * (sanitized.prometheus > 0 ? 1 : 0),
66
70
  atlas: Math.max(1, Math.round(rawHeights.atlas)) * (sanitized.atlas > 0 ? 1 : 0),
71
+ other: Math.max(1, Math.round(rawHeights.other)) * (sanitized.other > 0 ? 1 : 0),
67
72
  };
68
73
 
69
74
  // Ensure non-zero agents remain visible when possible
70
- let totalRounded = roundedHeights.sisyphus + roundedHeights.prometheus + roundedHeights.atlas;
75
+ let totalRounded = roundedHeights.sisyphus + roundedHeights.prometheus + roundedHeights.atlas + roundedHeights.other;
71
76
 
72
77
  // Distribute any overflow reduction fairly
73
78
  if (totalRounded > chartHeight) {
@@ -76,6 +81,7 @@ export function computeStackedSegments(
76
81
  { key: 'sisyphus' as keyof typeof roundedHeights, height: roundedHeights.sisyphus },
77
82
  { key: 'prometheus' as keyof typeof roundedHeights, height: roundedHeights.prometheus },
78
83
  { key: 'atlas' as keyof typeof roundedHeights, height: roundedHeights.atlas },
84
+ { key: 'other' as keyof typeof roundedHeights, height: roundedHeights.other },
79
85
  ].filter(w => w.height > 0);
80
86
 
81
87
  if (weights.length > 0) {
@@ -93,7 +99,7 @@ export function computeStackedSegments(
93
99
  }
94
100
 
95
101
  // If still over, trim from largest segments
96
- totalRounded = roundedHeights.sisyphus + roundedHeights.prometheus + roundedHeights.atlas;
102
+ totalRounded = roundedHeights.sisyphus + roundedHeights.prometheus + roundedHeights.atlas + roundedHeights.other;
97
103
  while (totalRounded > chartHeight) {
98
104
  const sortedWeights = weights
99
105
  .map(w => ({ ...w, height: roundedHeights[w.key] }))
@@ -141,5 +147,15 @@ export function computeStackedSegments(
141
147
  });
142
148
  }
143
149
 
150
+ // Other main agents (sand) - top
151
+ if (roundedHeights.other > 0) {
152
+ currentY -= roundedHeights.other;
153
+ segments.push({
154
+ tone: "sand",
155
+ y: currentY,
156
+ height: roundedHeights.other,
157
+ });
158
+ }
159
+
144
160
  return segments;
145
- }
161
+ }