gsd-pi 2.64.0-dev.b3ee078 → 2.64.0-dev.cc2cef3

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.
Files changed (44) hide show
  1. package/dist/resources/extensions/gsd/notification-overlay.js +39 -6
  2. package/dist/web/standalone/.next/BUILD_ID +1 -1
  3. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
  4. package/dist/web/standalone/.next/build-manifest.json +2 -2
  5. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  6. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  7. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  15. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/index.html +1 -1
  23. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
  30. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  31. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  32. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  33. package/package.json +1 -1
  34. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +4 -4
  35. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  36. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  37. package/packages/pi-tui/dist/overlay-layout.js +2 -2
  38. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  39. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +4 -4
  40. package/packages/pi-tui/src/overlay-layout.ts +2 -3
  41. package/src/resources/extensions/gsd/notification-overlay.ts +36 -7
  42. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +73 -0
  43. /package/dist/web/standalone/.next/static/{l7tiSF0KtXOwxxYn0ZAyF → kOmg_dJ8TT33Q12mP5qZc}/_buildManifest.js +0 -0
  44. /package/dist/web/standalone/.next/static/{l7tiSF0KtXOwxxYn0ZAyF → kOmg_dJ8TT33Q12mP5qZc}/_ssgManifest.js +0 -0
@@ -28,6 +28,27 @@ function severityIcon(severity: NotifySeverity): string {
28
28
  }
29
29
  }
30
30
 
31
+ /** Word-wrap plain text to fit within maxWidth columns. */
32
+ function wrapText(text: string, maxWidth: number): string[] {
33
+ if (text.length <= maxWidth) return [text];
34
+ const words = text.split(/\s+/);
35
+ const lines: string[] = [];
36
+ let current = "";
37
+ for (const word of words) {
38
+ if (current.length === 0) {
39
+ current = word;
40
+ } else if (current.length + 1 + word.length <= maxWidth) {
41
+ current += " " + word;
42
+ } else {
43
+ lines.push(current);
44
+ current = word;
45
+ }
46
+ }
47
+ if (current.length > 0) lines.push(current);
48
+ // If a single word exceeds maxWidth, truncate it
49
+ return lines.map((l) => l.length > maxWidth ? l.slice(0, maxWidth - 1) + "…" : l);
50
+ }
51
+
31
52
  function formatTimestamp(ts: string): string {
32
53
  try {
33
54
  const d = new Date(ts);
@@ -252,13 +273,21 @@ export class GSDNotificationOverlay {
252
273
  const time = th.fg("dim", formatTimestamp(entry.ts));
253
274
  const source = entry.source === "workflow-logger" ? th.fg("dim", " [engine]") : "";
254
275
 
255
- // First line: icon + timestamp + source
256
- const msgMaxWidth = contentWidth - 20;
257
- const msg = entry.message.length > msgMaxWidth
258
- ? entry.message.slice(0, msgMaxWidth - 1) + "…"
259
- : entry.message;
260
-
261
- lines.push(row(`${coloredIcon} ${time}${source} ${msg}`));
276
+ // Measure actual prefix width for wrapping
277
+ const prefix = `${coloredIcon} ${time}${source} `;
278
+ const prefixWidth = visibleWidth(prefix);
279
+ const msgMaxWidth = Math.max(10, contentWidth - prefixWidth);
280
+
281
+ // Wrap long messages onto continuation lines indented to align with message start
282
+ const msgLines = wrapText(entry.message, msgMaxWidth);
283
+ const indent = " ".repeat(prefixWidth);
284
+ for (let i = 0; i < msgLines.length; i++) {
285
+ if (i === 0) {
286
+ lines.push(row(`${prefix}${msgLines[i]}`));
287
+ } else {
288
+ lines.push(row(`${indent}${msgLines[i]}`));
289
+ }
290
+ }
262
291
  }
263
292
 
264
293
  return lines;
@@ -0,0 +1,73 @@
1
+ // GSD Extension — Notification Overlay Tests
2
+ // Tests for message wrapping and content-fit sizing in the notification panel.
3
+
4
+ import { describe, test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ // The wrapText function is private to the module, so we test the overlay's
8
+ // render output indirectly. We also extract and test wrapText logic directly.
9
+
10
+ // ── wrapText logic (mirrors the private function) ───────────────────────────
11
+
12
+ function wrapText(text: string, maxWidth: number): string[] {
13
+ if (text.length <= maxWidth) return [text];
14
+ const words = text.split(/\s+/);
15
+ const lines: string[] = [];
16
+ let current = "";
17
+ for (const word of words) {
18
+ if (current.length === 0) {
19
+ current = word;
20
+ } else if (current.length + 1 + word.length <= maxWidth) {
21
+ current += " " + word;
22
+ } else {
23
+ lines.push(current);
24
+ current = word;
25
+ }
26
+ }
27
+ if (current.length > 0) lines.push(current);
28
+ return lines.map((l) => l.length > maxWidth ? l.slice(0, maxWidth - 1) + "…" : l);
29
+ }
30
+
31
+ describe("notification overlay — wrapText", () => {
32
+ test("short text returns single line", () => {
33
+ const result = wrapText("hello world", 80);
34
+ assert.deepStrictEqual(result, ["hello world"]);
35
+ });
36
+
37
+ test("long text wraps at word boundaries", () => {
38
+ const text = "This is a long notification message that should wrap across multiple lines";
39
+ const result = wrapText(text, 40);
40
+ assert.ok(result.length > 1, `expected multiple lines, got ${result.length}`);
41
+ for (const line of result) {
42
+ assert.ok(line.length <= 40, `line exceeds maxWidth: "${line}" (${line.length})`);
43
+ }
44
+ });
45
+
46
+ test("single word exceeding maxWidth is truncated", () => {
47
+ const result = wrapText("superlongwordthatexceedsmaxwidth", 10);
48
+ assert.equal(result.length, 1);
49
+ assert.equal(result[0]!.length, 10);
50
+ assert.ok(result[0]!.endsWith("…"));
51
+ });
52
+
53
+ test("empty string returns single empty line", () => {
54
+ const result = wrapText("", 80);
55
+ assert.deepStrictEqual(result, [""]);
56
+ });
57
+
58
+ test("exact-fit text returns single line", () => {
59
+ const text = "exactly twenty chars";
60
+ const result = wrapText(text, 20);
61
+ assert.deepStrictEqual(result, [text]);
62
+ });
63
+
64
+ test("preserves all words across wrapped lines", () => {
65
+ const words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot"];
66
+ const text = words.join(" ");
67
+ const result = wrapText(text, 15);
68
+ const rejoined = result.join(" ");
69
+ for (const w of words) {
70
+ assert.ok(rejoined.includes(w), `missing word: ${w}`);
71
+ }
72
+ });
73
+ });