@f0rbit/overview 0.1.0 → 0.2.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/bin/overview ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ # Resolve symlink to find the actual package location
3
+ SOURCE="${BASH_SOURCE[0]}"
4
+ while [ -L "$SOURCE" ]; do
5
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
6
+ SOURCE="$(readlink "$SOURCE")"
7
+ [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
8
+ done
9
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
10
+ exec bun "$DIR/../packages/render/src/overview.tsx" "$@"
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@f0rbit/overview",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Terminal UI dashboard for multi-repo git health",
5
- "workspaces": ["packages/*"],
5
+ "workspaces": [
6
+ "packages/*"
7
+ ],
6
8
  "bin": {
7
- "overview": "packages/render/src/overview.tsx"
9
+ "overview": "bin/overview"
8
10
  },
9
11
  "files": [
10
12
  "packages/",
13
+ "bin/",
11
14
  "bunfig.toml",
12
15
  "tsconfig.json"
13
16
  ],
@@ -23,7 +26,14 @@
23
26
  "url": "https://github.com/f0rbit/overview.git"
24
27
  },
25
28
  "license": "MIT",
26
- "keywords": ["git", "tui", "terminal", "dashboard", "bun", "solidjs"],
29
+ "keywords": [
30
+ "git",
31
+ "tui",
32
+ "terminal",
33
+ "dashboard",
34
+ "bun",
35
+ "solidjs"
36
+ ],
27
37
  "engines": {
28
38
  "bun": ">=1.0.0"
29
39
  },
@@ -7,7 +7,7 @@
7
7
  "typecheck": "tsc --noEmit"
8
8
  },
9
9
  "dependencies": {
10
- "@f0rbit/corpus": "link:@f0rbit/corpus",
11
- "@devpad/api": "link:@devpad/api"
10
+ "@f0rbit/corpus": "^0.3.5",
11
+ "@devpad/api": "^2.1.3"
12
12
  }
13
13
  }
@@ -12,7 +12,7 @@
12
12
  "@opentui/solid": "^0.1.80",
13
13
  "solid-js": "^1.9.11",
14
14
  "@overview/core": "workspace:*",
15
- "@f0rbit/corpus": "link:@f0rbit/corpus",
16
- "@devpad/api": "link:@devpad/api"
15
+ "@f0rbit/corpus": "^0.3.5",
16
+ "@devpad/api": "^2.1.3"
17
17
  }
18
18
  }
@@ -50,6 +50,8 @@ function mockRepoStatus(): RepoStatus {
50
50
  time: Date.now() / 1000,
51
51
  },
52
52
  ],
53
+ commit_activity: null,
54
+ ocn_status: null,
53
55
  is_clean: false,
54
56
  health: "ahead" as const,
55
57
  };
@@ -16,7 +16,7 @@ describe("createFetchContext", () => {
16
16
  // Wait for debounce
17
17
  await Bun.sleep(80);
18
18
 
19
- expect(result).toBe("hello");
19
+ expect(result!).toBe("hello");
20
20
  ctx.dispose();
21
21
  });
22
22
 
@@ -46,7 +46,7 @@ describe("createFetchContext", () => {
46
46
 
47
47
  await Bun.sleep(10);
48
48
 
49
- expect(result).toBe("now");
49
+ expect(result!).toBe("now");
50
50
  ctx.dispose();
51
51
  });
52
52
 
@@ -88,10 +88,10 @@ describe("computeRows", () => {
88
88
  const rows = computeRows(widgets, 80);
89
89
 
90
90
  expect(rows).toHaveLength(1);
91
- expect(rows[0].columns).toBe(2);
92
- expect(rows[0].widgets).toHaveLength(2);
93
- expect(rows[0].widgets[0].id).toBe("git-status");
94
- expect(rows[0].widgets[1].id).toBe("repo-meta");
91
+ expect(rows[0]!.columns).toBe(2);
92
+ expect(rows[0]!.widgets).toHaveLength(2);
93
+ expect(rows[0]!.widgets[0]!.id).toBe("git-status");
94
+ expect(rows[0]!.widgets[1]!.id).toBe("repo-meta");
95
95
  });
96
96
 
97
97
  test("half + full → sorted by priority: half(p0) before full(p1)", () => {
@@ -102,10 +102,10 @@ describe("computeRows", () => {
102
102
  const rows = computeRows(widgets, 80);
103
103
 
104
104
  expect(rows).toHaveLength(2);
105
- expect(rows[0].columns).toBe(1);
106
- expect(rows[0].widgets[0].id).toBe("git-status");
107
- expect(rows[1].columns).toBe(1);
108
- expect(rows[1].widgets[0].id).toBe("recent-commits");
105
+ expect(rows[0]!.columns).toBe(1);
106
+ expect(rows[0]!.widgets[0]!.id).toBe("git-status");
107
+ expect(rows[1]!.columns).toBe(1);
108
+ expect(rows[1]!.widgets[0]!.id).toBe("recent-commits");
109
109
  });
110
110
 
111
111
  test("full + half + half → full(p0) first, half pair(p1) second", () => {
@@ -117,11 +117,11 @@ describe("computeRows", () => {
117
117
  const rows = computeRows(widgets, 80);
118
118
 
119
119
  expect(rows).toHaveLength(2);
120
- expect(rows[0].columns).toBe(1);
121
- expect(rows[0].widgets[0].id).toBe("recent-commits");
122
- expect(rows[1].columns).toBe(2);
123
- expect(rows[1].widgets[0].id).toBe("git-status");
124
- expect(rows[1].widgets[1].id).toBe("repo-meta");
120
+ expect(rows[0]!.columns).toBe(1);
121
+ expect(rows[0]!.widgets[0]!.id).toBe("recent-commits");
122
+ expect(rows[1]!.columns).toBe(2);
123
+ expect(rows[1]!.widgets[0]!.id).toBe("git-status");
124
+ expect(rows[1]!.widgets[1]!.id).toBe("repo-meta");
125
125
  });
126
126
 
127
127
  test("three half-width → 2-column row + 1-column row", () => {
@@ -133,11 +133,11 @@ describe("computeRows", () => {
133
133
  const rows = computeRows(widgets, 80);
134
134
 
135
135
  expect(rows).toHaveLength(2);
136
- expect(rows[0].columns).toBe(2);
137
- expect(rows[0].widgets).toHaveLength(2);
138
- expect(rows[1].columns).toBe(1);
139
- expect(rows[1].widgets).toHaveLength(1);
140
- expect(rows[1].widgets[0].id).toBe("branch-list");
136
+ expect(rows[0]!.columns).toBe(2);
137
+ expect(rows[0]!.widgets).toHaveLength(2);
138
+ expect(rows[1]!.columns).toBe(1);
139
+ expect(rows[1]!.widgets).toHaveLength(1);
140
+ expect(rows[1]!.widgets[0]!.id).toBe("branch-list");
141
141
  });
142
142
 
143
143
  test("single full-width → 1 one-column row", () => {
@@ -145,8 +145,8 @@ describe("computeRows", () => {
145
145
  const rows = computeRows(widgets, 80);
146
146
 
147
147
  expect(rows).toHaveLength(1);
148
- expect(rows[0].columns).toBe(1);
149
- expect(rows[0].widgets[0].id).toBe("recent-commits");
148
+ expect(rows[0]!.columns).toBe(1);
149
+ expect(rows[0]!.widgets[0]!.id).toBe("recent-commits");
150
150
  });
151
151
 
152
152
  test("narrow panel (< 40) → all half become full, each own row", () => {
@@ -177,9 +177,9 @@ describe("computeRows", () => {
177
177
  const rows = computeRows(widgets, 80);
178
178
 
179
179
  expect(rows).toHaveLength(1);
180
- expect(rows[0].columns).toBe(2);
181
- expect(rows[0].widgets[0].id).toBe("git-status");
182
- expect(rows[0].widgets[1].id).toBe("branch-list");
180
+ expect(rows[0]!.columns).toBe(2);
181
+ expect(rows[0]!.widgets[0]!.id).toBe("git-status");
182
+ expect(rows[0]!.widgets[1]!.id).toBe("branch-list");
183
183
  });
184
184
 
185
185
  test("order is preserved for same-type widgets", () => {
@@ -192,10 +192,10 @@ describe("computeRows", () => {
192
192
  const rows = computeRows(widgets, 80);
193
193
 
194
194
  expect(rows).toHaveLength(2);
195
- expect(rows[0].widgets[0].id).toBe("github-prs");
196
- expect(rows[0].widgets[1].id).toBe("github-issues");
197
- expect(rows[1].widgets[0].id).toBe("github-ci");
198
- expect(rows[1].widgets[1].id).toBe("devpad-tasks");
195
+ expect(rows[0]!.widgets[0]!.id).toBe("github-prs");
196
+ expect(rows[0]!.widgets[1]!.id).toBe("github-issues");
197
+ expect(rows[1]!.widgets[0]!.id).toBe("github-ci");
198
+ expect(rows[1]!.widgets[1]!.id).toBe("devpad-tasks");
199
199
  });
200
200
 
201
201
  test("non-contiguous halfs pair together", () => {
@@ -208,11 +208,11 @@ describe("computeRows", () => {
208
208
 
209
209
  expect(rows).toHaveLength(2);
210
210
  // half pair has min priority 0, full has priority 1 → pair first
211
- expect(rows[0].columns).toBe(2);
212
- expect(rows[0].widgets[0].id).toBe("git-status");
213
- expect(rows[0].widgets[1].id).toBe("repo-meta");
214
- expect(rows[1].columns).toBe(1);
215
- expect(rows[1].widgets[0].id).toBe("recent-commits");
211
+ expect(rows[0]!.columns).toBe(2);
212
+ expect(rows[0]!.widgets[0]!.id).toBe("git-status");
213
+ expect(rows[0]!.widgets[1]!.id).toBe("repo-meta");
214
+ expect(rows[1]!.columns).toBe(1);
215
+ expect(rows[1]!.widgets[0]!.id).toBe("recent-commits");
216
216
  });
217
217
 
218
218
  test("full-width at priority 0 comes before halfs at priority 3+4", () => {
@@ -224,11 +224,11 @@ describe("computeRows", () => {
224
224
  const rows = computeRows(widgets, 80);
225
225
 
226
226
  expect(rows).toHaveLength(2);
227
- expect(rows[0].columns).toBe(1);
228
- expect(rows[0].widgets[0].id).toBe("recent-commits");
229
- expect(rows[1].columns).toBe(2);
230
- expect(rows[1].widgets[0].id).toBe("git-status");
231
- expect(rows[1].widgets[1].id).toBe("repo-meta");
227
+ expect(rows[0]!.columns).toBe(1);
228
+ expect(rows[0]!.widgets[0]!.id).toBe("recent-commits");
229
+ expect(rows[1]!.columns).toBe(2);
230
+ expect(rows[1]!.widgets[0]!.id).toBe("git-status");
231
+ expect(rows[1]!.widgets[1]!.id).toBe("repo-meta");
232
232
  });
233
233
 
234
234
  test("odd number of halfs → trailing half gets 1-column row", () => {
@@ -242,14 +242,14 @@ describe("computeRows", () => {
242
242
  const rows = computeRows(widgets, 80);
243
243
 
244
244
  expect(rows).toHaveLength(3);
245
- expect(rows[0].columns).toBe(2);
246
- expect(rows[0].widgets[0].id).toBe("git-status");
247
- expect(rows[0].widgets[1].id).toBe("repo-meta");
248
- expect(rows[1].columns).toBe(2);
249
- expect(rows[1].widgets[0].id).toBe("branch-list");
250
- expect(rows[1].widgets[1].id).toBe("recent-commits");
251
- expect(rows[2].columns).toBe(1);
252
- expect(rows[2].widgets[0].id).toBe("github-prs");
245
+ expect(rows[0]!.columns).toBe(2);
246
+ expect(rows[0]!.widgets[0]!.id).toBe("git-status");
247
+ expect(rows[0]!.widgets[1]!.id).toBe("repo-meta");
248
+ expect(rows[1]!.columns).toBe(2);
249
+ expect(rows[1]!.widgets[0]!.id).toBe("branch-list");
250
+ expect(rows[1]!.widgets[1]!.id).toBe("recent-commits");
251
+ expect(rows[2]!.columns).toBe(1);
252
+ expect(rows[2]!.widgets[0]!.id).toBe("github-prs");
253
253
  });
254
254
 
255
255
  test("three third-width widgets → 1 three-column row", () => {
@@ -261,11 +261,11 @@ describe("computeRows", () => {
261
261
  const rows = computeRows(widgets, 80);
262
262
 
263
263
  expect(rows).toHaveLength(1);
264
- expect(rows[0].columns).toBe(3);
265
- expect(rows[0].widgets).toHaveLength(3);
266
- expect(rows[0].widgets[0].id).toBe("git-status");
267
- expect(rows[0].widgets[1].id).toBe("repo-meta");
268
- expect(rows[0].widgets[2].id).toBe("github-ci");
264
+ expect(rows[0]!.columns).toBe(3);
265
+ expect(rows[0]!.widgets).toHaveLength(3);
266
+ expect(rows[0]!.widgets[0]!.id).toBe("git-status");
267
+ expect(rows[0]!.widgets[1]!.id).toBe("repo-meta");
268
+ expect(rows[0]!.widgets[2]!.id).toBe("github-ci");
269
269
  });
270
270
 
271
271
  test("four thirds → 3-col row + 1-col row (auto-expand)", () => {
@@ -278,11 +278,11 @@ describe("computeRows", () => {
278
278
  const rows = computeRows(widgets, 80);
279
279
 
280
280
  expect(rows).toHaveLength(2);
281
- expect(rows[0].columns).toBe(3);
282
- expect(rows[0].widgets).toHaveLength(3);
283
- expect(rows[1].columns).toBe(1);
284
- expect(rows[1].widgets).toHaveLength(1);
285
- expect(rows[1].widgets[0].id).toBe("commit-activity");
281
+ expect(rows[0]!.columns).toBe(3);
282
+ expect(rows[0]!.widgets).toHaveLength(3);
283
+ expect(rows[1]!.columns).toBe(1);
284
+ expect(rows[1]!.widgets).toHaveLength(1);
285
+ expect(rows[1]!.widgets[0]!.id).toBe("commit-activity");
286
286
  });
287
287
 
288
288
  test("five thirds → 3-col row + 2-col row (auto-expand)", () => {
@@ -296,12 +296,12 @@ describe("computeRows", () => {
296
296
  const rows = computeRows(widgets, 80);
297
297
 
298
298
  expect(rows).toHaveLength(2);
299
- expect(rows[0].columns).toBe(3);
300
- expect(rows[0].widgets).toHaveLength(3);
301
- expect(rows[1].columns).toBe(2);
302
- expect(rows[1].widgets).toHaveLength(2);
303
- expect(rows[1].widgets[0].id).toBe("commit-activity");
304
- expect(rows[1].widgets[1].id).toBe("github-release");
299
+ expect(rows[0]!.columns).toBe(3);
300
+ expect(rows[0]!.widgets).toHaveLength(3);
301
+ expect(rows[1]!.columns).toBe(2);
302
+ expect(rows[1]!.widgets).toHaveLength(2);
303
+ expect(rows[1]!.widgets[0]!.id).toBe("commit-activity");
304
+ expect(rows[1]!.widgets[1]!.id).toBe("github-release");
305
305
  });
306
306
 
307
307
  test("mixed thirds + halfs + fulls", () => {
@@ -317,12 +317,12 @@ describe("computeRows", () => {
317
317
 
318
318
  expect(rows).toHaveLength(3);
319
319
  // Sort by min priority: full(0), thirds(1), halfs(4)
320
- expect(rows[0].columns).toBe(1);
321
- expect(rows[0].widgets[0].id).toBe("recent-commits");
322
- expect(rows[1].columns).toBe(3);
323
- expect(rows[1].widgets[0].id).toBe("git-status");
324
- expect(rows[2].columns).toBe(2);
325
- expect(rows[2].widgets[0].id).toBe("branch-list");
320
+ expect(rows[0]!.columns).toBe(1);
321
+ expect(rows[0]!.widgets[0]!.id).toBe("recent-commits");
322
+ expect(rows[1]!.columns).toBe(3);
323
+ expect(rows[1]!.widgets[0]!.id).toBe("git-status");
324
+ expect(rows[2]!.columns).toBe(2);
325
+ expect(rows[2]!.widgets[0]!.id).toBe("branch-list");
326
326
  });
327
327
 
328
328
  test("thirds at narrow panel (<60) fall back to half pairing", () => {
@@ -335,10 +335,10 @@ describe("computeRows", () => {
335
335
 
336
336
  // thirds resolve to half at 50 cols → paired as halfs
337
337
  expect(rows).toHaveLength(2);
338
- expect(rows[0].columns).toBe(2);
339
- expect(rows[0].widgets).toHaveLength(2);
340
- expect(rows[1].columns).toBe(1);
341
- expect(rows[1].widgets).toHaveLength(1);
338
+ expect(rows[0]!.columns).toBe(2);
339
+ expect(rows[0]!.widgets).toHaveLength(2);
340
+ expect(rows[1]!.columns).toBe(1);
341
+ expect(rows[1]!.widgets).toHaveLength(1);
342
342
  });
343
343
 
344
344
  test("thirds at very narrow panel (<40) fall back to full", () => {
@@ -370,71 +370,71 @@ describe("buildBorderLine", () => {
370
370
  test("top border, 1-col next row → corners + horizontal fill", () => {
371
371
  const line = buildBorderLine("top", W, null, oneCol);
372
372
  expect(line.length).toBe(W);
373
- expect(line[0]).toBe("╭");
374
- expect(line[W - 1]).toBe("╮");
373
+ expect(line[0]!).toBe("╭");
374
+ expect(line[W - 1]!).toBe("╮");
375
375
  // No junction — all interior chars are horizontal
376
376
  for (let i = 1; i < W - 1; i++) {
377
- expect(line[i]).toBe("─");
377
+ expect(line[i]!).toBe("─");
378
378
  }
379
379
  });
380
380
 
381
381
  test("top border, 2-col next row → has ┬ at midpoint", () => {
382
382
  const line = buildBorderLine("top", W, null, twoCol);
383
383
  expect(line.length).toBe(W);
384
- expect(line[0]).toBe("╭");
385
- expect(line[W - 1]).toBe("╮");
386
- expect(line[10]).toBe("┬");
384
+ expect(line[0]!).toBe("╭");
385
+ expect(line[W - 1]!).toBe("╮");
386
+ expect(line[10]!).toBe("┬");
387
387
  });
388
388
 
389
389
  test("bottom border, 1-col prev row → corners only", () => {
390
390
  const line = buildBorderLine("bottom", W, oneCol, null);
391
391
  expect(line.length).toBe(W);
392
- expect(line[0]).toBe("╰");
393
- expect(line[W - 1]).toBe("╯");
392
+ expect(line[0]!).toBe("╰");
393
+ expect(line[W - 1]!).toBe("╯");
394
394
  for (let i = 1; i < W - 1; i++) {
395
- expect(line[i]).toBe("─");
395
+ expect(line[i]!).toBe("─");
396
396
  }
397
397
  });
398
398
 
399
399
  test("bottom border, 2-col prev row → has ┴ at midpoint", () => {
400
400
  const line = buildBorderLine("bottom", W, twoCol, null);
401
401
  expect(line.length).toBe(W);
402
- expect(line[0]).toBe("╰");
403
- expect(line[W - 1]).toBe("╯");
404
- expect(line[10]).toBe("┴");
402
+ expect(line[0]!).toBe("╰");
403
+ expect(line[W - 1]!).toBe("╯");
404
+ expect(line[10]!).toBe("┴");
405
405
  });
406
406
 
407
407
  test("mid border, 2-col prev → 1-col next → ┴ at midpoint", () => {
408
408
  const line = buildBorderLine("mid", W, twoCol, oneCol);
409
409
  expect(line.length).toBe(W);
410
- expect(line[0]).toBe("├");
411
- expect(line[W - 1]).toBe("┤");
412
- expect(line[10]).toBe("┴");
410
+ expect(line[0]!).toBe("├");
411
+ expect(line[W - 1]!).toBe("┤");
412
+ expect(line[10]!).toBe("┴");
413
413
  });
414
414
 
415
415
  test("mid border, 1-col prev → 2-col next → ┬ at midpoint", () => {
416
416
  const line = buildBorderLine("mid", W, oneCol, twoCol);
417
417
  expect(line.length).toBe(W);
418
- expect(line[0]).toBe("├");
419
- expect(line[W - 1]).toBe("┤");
420
- expect(line[10]).toBe("┬");
418
+ expect(line[0]!).toBe("├");
419
+ expect(line[W - 1]!).toBe("┤");
420
+ expect(line[10]!).toBe("┬");
421
421
  });
422
422
 
423
423
  test("mid border, 2-col prev → 2-col next → ┼ at midpoint", () => {
424
424
  const line = buildBorderLine("mid", W, twoCol, twoCol);
425
425
  expect(line.length).toBe(W);
426
- expect(line[0]).toBe("├");
427
- expect(line[W - 1]).toBe("┤");
428
- expect(line[10]).toBe("┼");
426
+ expect(line[0]!).toBe("├");
427
+ expect(line[W - 1]!).toBe("┤");
428
+ expect(line[10]!).toBe("┼");
429
429
  });
430
430
 
431
431
  test("mid border, 1-col prev → 1-col next → no junction", () => {
432
432
  const line = buildBorderLine("mid", W, oneCol, oneCol);
433
433
  expect(line.length).toBe(W);
434
- expect(line[0]).toBe("├");
435
- expect(line[W - 1]).toBe("┤");
434
+ expect(line[0]!).toBe("├");
435
+ expect(line[W - 1]!).toBe("┤");
436
436
  for (let i = 1; i < W - 1; i++) {
437
- expect(line[i]).toBe("─");
437
+ expect(line[i]!).toBe("─");
438
438
  }
439
439
  });
440
440
 
@@ -455,8 +455,8 @@ describe("buildBorderLine", () => {
455
455
  ];
456
456
  for (const [type, left, right] of cases) {
457
457
  const line = buildBorderLine(type, W, oneCol, oneCol);
458
- expect(line[0]).toBe(left);
459
- expect(line[W - 1]).toBe(right);
458
+ expect(line[0]!).toBe(left);
459
+ expect(line[W - 1]!).toBe(right);
460
460
  }
461
461
  });
462
462
 
@@ -464,59 +464,59 @@ describe("buildBorderLine", () => {
464
464
  const odd_w = 21;
465
465
  const line = buildBorderLine("top", odd_w, null, twoCol);
466
466
  expect(line.length).toBe(odd_w);
467
- expect(line[Math.floor(odd_w / 2)]).toBe("┬");
468
- expect(line[0]).toBe("╭");
469
- expect(line[odd_w - 1]).toBe("╮");
467
+ expect(line[Math.floor(odd_w / 2)]!).toBe("┬");
468
+ expect(line[0]!).toBe("╭");
469
+ expect(line[odd_w - 1]!).toBe("╮");
470
470
  });
471
471
 
472
472
  test("top border, 3-col next row → has two junctions", () => {
473
473
  const W = 30;
474
474
  const line = buildBorderLine("top", W, null, threeCol);
475
475
  expect(line.length).toBe(W);
476
- expect(line[0]).toBe("╭");
477
- expect(line[W - 1]).toBe("╮");
478
- expect(line[10]).toBe("┬"); // Math.floor(30/3) = 10
479
- expect(line[20]).toBe("┬"); // Math.floor(60/3) = 20
476
+ expect(line[0]!).toBe("╭");
477
+ expect(line[W - 1]!).toBe("╮");
478
+ expect(line[10]!).toBe("┬"); // Math.floor(30/3) = 10
479
+ expect(line[20]!).toBe("┬"); // Math.floor(60/3) = 20
480
480
  });
481
481
 
482
482
  test("bottom border, 3-col prev row → has two ┴ junctions", () => {
483
483
  const W = 30;
484
484
  const line = buildBorderLine("bottom", W, threeCol, null);
485
485
  expect(line.length).toBe(W);
486
- expect(line[0]).toBe("╰");
487
- expect(line[W - 1]).toBe("╯");
488
- expect(line[10]).toBe("┴");
489
- expect(line[20]).toBe("┴");
486
+ expect(line[0]!).toBe("╰");
487
+ expect(line[W - 1]!).toBe("╯");
488
+ expect(line[10]!).toBe("┴");
489
+ expect(line[20]!).toBe("┴");
490
490
  });
491
491
 
492
492
  test("mid border, 3-col → 3-col → has two ┼ junctions", () => {
493
493
  const W = 30;
494
494
  const line = buildBorderLine("mid", W, threeCol, threeCol);
495
495
  expect(line.length).toBe(W);
496
- expect(line[0]).toBe("├");
497
- expect(line[W - 1]).toBe("┤");
498
- expect(line[10]).toBe("┼");
499
- expect(line[20]).toBe("┼");
496
+ expect(line[0]!).toBe("├");
497
+ expect(line[W - 1]!).toBe("┤");
498
+ expect(line[10]!).toBe("┼");
499
+ expect(line[20]!).toBe("┼");
500
500
  });
501
501
 
502
502
  test("mid border, 3-col → 1-col → two ┴ junctions", () => {
503
503
  const W = 30;
504
504
  const line = buildBorderLine("mid", W, threeCol, oneCol);
505
505
  expect(line.length).toBe(W);
506
- expect(line[0]).toBe("├");
507
- expect(line[W - 1]).toBe("┤");
508
- expect(line[10]).toBe("┴");
509
- expect(line[20]).toBe("┴");
506
+ expect(line[0]!).toBe("├");
507
+ expect(line[W - 1]!).toBe("┤");
508
+ expect(line[10]!).toBe("┴");
509
+ expect(line[20]!).toBe("┴");
510
510
  });
511
511
 
512
512
  test("mid border, 1-col → 3-col → two ┬ junctions", () => {
513
513
  const W = 30;
514
514
  const line = buildBorderLine("mid", W, oneCol, threeCol);
515
515
  expect(line.length).toBe(W);
516
- expect(line[0]).toBe("├");
517
- expect(line[W - 1]).toBe("┤");
518
- expect(line[10]).toBe("┬");
519
- expect(line[20]).toBe("┬");
516
+ expect(line[0]!).toBe("├");
517
+ expect(line[W - 1]!).toBe("┤");
518
+ expect(line[10]!).toBe("┬");
519
+ expect(line[20]!).toBe("┬");
520
520
  });
521
521
 
522
522
  test("mid border, 3-col → 2-col → mixed junctions", () => {
@@ -528,22 +528,22 @@ describe("buildBorderLine", () => {
528
528
  const W = 30;
529
529
  const line = buildBorderLine("mid", W, threeCol, twoCol);
530
530
  expect(line.length).toBe(W);
531
- expect(line[0]).toBe("├");
532
- expect(line[W - 1]).toBe("┤");
533
- expect(line[10]).toBe("┴"); // from 3-col above only
534
- expect(line[15]).toBe("┬"); // from 2-col below only
535
- expect(line[20]).toBe("┴"); // from 3-col above only
531
+ expect(line[0]!).toBe("├");
532
+ expect(line[W - 1]!).toBe("┤");
533
+ expect(line[10]!).toBe("┴"); // from 3-col above only
534
+ expect(line[15]!).toBe("┬"); // from 2-col below only
535
+ expect(line[20]!).toBe("┴"); // from 3-col above only
536
536
  });
537
537
 
538
538
  test("mid border, 2-col → 3-col → mixed junctions", () => {
539
539
  const W = 30;
540
540
  const line = buildBorderLine("mid", W, twoCol, threeCol);
541
541
  expect(line.length).toBe(W);
542
- expect(line[0]).toBe("├");
543
- expect(line[W - 1]).toBe("┤");
544
- expect(line[10]).toBe("┬"); // from 3-col below only
545
- expect(line[15]).toBe("┴"); // from 2-col above only
546
- expect(line[20]).toBe("┬"); // from 3-col below only
542
+ expect(line[0]!).toBe("├");
543
+ expect(line[W - 1]!).toBe("┤");
544
+ expect(line[10]!).toBe("┬"); // from 3-col below only
545
+ expect(line[15]!).toBe("┴"); // from 2-col above only
546
+ expect(line[20]!).toBe("┬"); // from 3-col below only
547
547
  });
548
548
  });
549
549
 
@@ -555,9 +555,9 @@ describe("buildBorderLineWithTitle", () => {
555
555
  test("inserts title into border line after first char", () => {
556
556
  const result = buildBorderLineWithTitle(base, "Hello");
557
557
  expect(result.length).toBe(20);
558
- expect(result[0]).toBe("╭");
558
+ expect(result[0]!).toBe("╭");
559
559
  expect(result.slice(1, 8)).toBe(" Hello ");
560
- expect(result[19]).toBe("╮");
560
+ expect(result[19]!).toBe("╮");
561
561
  });
562
562
 
563
563
  test("empty title returns unchanged line", () => {
@@ -569,8 +569,8 @@ describe("buildBorderLineWithTitle", () => {
569
569
  const result = buildBorderLineWithTitle(base, long_title);
570
570
  expect(result.length).toBe(20);
571
571
  expect(result).toContain("…");
572
- expect(result[0]).toBe("╭");
573
- expect(result[19]).toBe("╮");
572
+ expect(result[0]!).toBe("╭");
573
+ expect(result[19]!).toBe("╮");
574
574
  });
575
575
 
576
576
  test("title fits exactly (boundary)", () => {
@@ -580,8 +580,8 @@ describe("buildBorderLineWithTitle", () => {
580
580
  const exact_title = "A".repeat(15);
581
581
  const result = buildBorderLineWithTitle(base, exact_title);
582
582
  expect(result.length).toBe(20);
583
- expect(result[0]).toBe("╭");
584
- expect(result[19]).toBe("╮");
583
+ expect(result[0]!).toBe("╭");
584
+ expect(result[19]!).toBe("╮");
585
585
  expect(result).toContain(exact_title);
586
586
  expect(result).not.toContain("…");
587
587
  });