@dungle-scrubs/tallow 0.9.4 → 0.9.7

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 (212) hide show
  1. package/dist/cli.js +8 -5
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/interactive-mode-patch.d.ts +24 -12
  6. package/dist/interactive-mode-patch.d.ts.map +1 -1
  7. package/dist/interactive-mode-patch.js +229 -146
  8. package/dist/interactive-mode-patch.js.map +1 -1
  9. package/dist/interactive-reset.d.ts +49 -0
  10. package/dist/interactive-reset.d.ts.map +1 -0
  11. package/dist/interactive-reset.js +40 -0
  12. package/dist/interactive-reset.js.map +1 -0
  13. package/dist/pi-tui-editor-patch.d.ts +10 -0
  14. package/dist/pi-tui-editor-patch.d.ts.map +1 -0
  15. package/dist/pi-tui-editor-patch.js +159 -0
  16. package/dist/pi-tui-editor-patch.js.map +1 -0
  17. package/dist/pi-tui-patch.d.ts +2 -0
  18. package/dist/pi-tui-patch.d.ts.map +1 -0
  19. package/dist/pi-tui-patch.js +563 -0
  20. package/dist/pi-tui-patch.js.map +1 -0
  21. package/dist/pi-tui-settings-list-patch.d.ts +11 -0
  22. package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
  23. package/dist/pi-tui-settings-list-patch.js +38 -0
  24. package/dist/pi-tui-settings-list-patch.js.map +1 -0
  25. package/dist/process-cleanup.js +1 -1
  26. package/dist/process-cleanup.js.map +1 -1
  27. package/dist/reset-diagnostics.d.ts +69 -0
  28. package/dist/reset-diagnostics.d.ts.map +1 -0
  29. package/dist/reset-diagnostics.js +41 -0
  30. package/dist/reset-diagnostics.js.map +1 -0
  31. package/dist/sdk.d.ts +7 -23
  32. package/dist/sdk.d.ts.map +1 -1
  33. package/dist/sdk.js +211 -174
  34. package/dist/sdk.js.map +1 -1
  35. package/dist/workspace-transition-interactive.d.ts +1 -0
  36. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  37. package/dist/workspace-transition-interactive.js +8 -18
  38. package/dist/workspace-transition-interactive.js.map +1 -1
  39. package/extensions/__integration__/audit-findings.test.ts +4 -5
  40. package/extensions/_icons/index.ts +2 -4
  41. package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
  42. package/extensions/_shared/__tests__/shell-policy.test.ts +19 -0
  43. package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
  44. package/extensions/_shared/image-metadata.ts +99 -0
  45. package/extensions/_shared/inline-preview.ts +1 -1
  46. package/extensions/_shared/shell-policy.ts +121 -1
  47. package/extensions/_shared/terminal-links.ts +22 -0
  48. package/extensions/ask-user-question-tool/index.ts +0 -3
  49. package/extensions/clear/__tests__/clear.test.ts +269 -2
  50. package/extensions/command-expansion/index.ts +9 -3
  51. package/extensions/context-files/index.ts +5 -1
  52. package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
  53. package/extensions/context-fork/extension.json +1 -1
  54. package/extensions/context-fork/frontmatter-index.ts +6 -1
  55. package/extensions/context-fork/index.ts +32 -0
  56. package/extensions/edit-tool-enhanced/index.ts +2 -1
  57. package/extensions/git-status/__tests__/git-status.test.ts +65 -2
  58. package/extensions/git-status/index.ts +268 -98
  59. package/extensions/hooks/index.ts +33 -11
  60. package/extensions/loop/index.ts +14 -1
  61. package/extensions/lsp/index.ts +64 -13
  62. package/extensions/lsp/package.json +2 -2
  63. package/extensions/minimal-skill-display/index.ts +7 -1
  64. package/extensions/random-spinner/index.ts +7 -642
  65. package/extensions/read-tool-enhanced/index.ts +13 -10
  66. package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
  67. package/extensions/render-stabilizer/index.ts +6 -6
  68. package/extensions/rewind/__tests__/session-files.test.ts +115 -0
  69. package/extensions/rewind/__tests__/snapshots.test.ts +23 -0
  70. package/extensions/rewind/index.ts +5 -0
  71. package/extensions/rewind/session-files.ts +138 -0
  72. package/extensions/rewind/snapshots.ts +104 -5
  73. package/extensions/skill-commands/index.ts +6 -1
  74. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
  75. package/extensions/slash-command-bridge/index.ts +14 -2
  76. package/extensions/subagent-tool/model-resolver.ts +274 -7
  77. package/extensions/subagent-tool/schema.ts +1 -2
  78. package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
  79. package/extensions/teams-tool/tools/register-extension.ts +1 -3
  80. package/extensions/teams-tool/tools/teammate-tools.ts +1 -2
  81. package/extensions/web-search-tool/index.ts +2 -1
  82. package/extensions/wezterm-pane-control/index.ts +1 -2
  83. package/extensions/write-tool-enhanced/index.ts +2 -1
  84. package/node_modules/@mariozechner/pi-tui/README.md +56 -34
  85. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
  86. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
  87. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
  88. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
  89. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
  90. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
  91. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
  92. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
  93. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
  94. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
  95. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
  96. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
  97. package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
  98. package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
  99. package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
  100. package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
  101. package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
  102. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
  103. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
  104. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
  105. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
  106. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
  107. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
  108. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
  109. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
  110. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
  111. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
  112. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
  113. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
  114. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
  115. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
  116. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
  117. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
  118. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  119. package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
  120. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  121. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
  122. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  123. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
  124. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  125. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
  126. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  127. package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
  128. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  129. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
  130. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
  131. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
  132. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
  133. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
  134. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  135. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
  136. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  137. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
  138. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  139. package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
  140. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  141. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
  142. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
  143. package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
  144. package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
  145. package/node_modules/@mariozechner/pi-tui/package.json +6 -6
  146. package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
  147. package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
  148. package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
  149. package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
  150. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
  151. package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
  152. package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
  153. package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
  154. package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
  155. package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
  156. package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
  157. package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
  158. package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
  159. package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
  160. package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
  161. package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
  162. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
  163. package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
  164. package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
  165. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
  166. package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
  167. package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
  168. package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
  169. package/package.json +13 -13
  170. package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
  171. package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
  172. package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
  173. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
  174. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
  175. package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
  176. package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
  177. package/packages/tallow-tui/node_modules/marked/README.md +5 -4
  178. package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
  179. package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
  180. package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
  181. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
  182. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
  183. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
  184. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
  185. package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
  186. package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
  187. package/packages/tallow-tui/node_modules/marked/package.json +26 -34
  188. package/skills/tallow-expert/SKILL.md +3 -5
  189. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
  190. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
  191. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
  192. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
  193. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
  194. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
  195. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
  196. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
  197. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
  198. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
  199. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
  200. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
  201. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
  202. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
  203. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
  204. package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
  205. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
  206. package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
  207. package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
  208. package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
  209. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
  210. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
  211. package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
  212. package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
@@ -1,185 +1,56 @@
1
1
  /**
2
- * Tests for tallow-tui core utility functions: visibleWidth, truncateToWidth,
3
- * wrapTextWithAnsi, hyperlink, and fileLink.
2
+ * Tests for core utility functions kept in the fork.
4
3
  */
5
4
  import { describe, expect, it } from "bun:test";
6
- import { fileLink, hyperlink, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils.js";
7
-
8
- // ── visibleWidth ─────────────────────────────────────────────────────────────
5
+ import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils.js";
9
6
 
10
7
  describe("visibleWidth", () => {
11
8
  it("counts ASCII characters", () => {
12
9
  expect(visibleWidth("hello")).toBe(5);
13
10
  });
14
11
 
15
- it("returns 0 for empty string", () => {
16
- expect(visibleWidth("")).toBe(0);
17
- });
18
-
19
12
  it("ignores SGR color codes", () => {
20
13
  expect(visibleWidth("\x1b[31mred\x1b[0m")).toBe(3);
21
14
  });
22
15
 
23
- it("ignores nested ANSI codes", () => {
24
- expect(visibleWidth("\x1b[1m\x1b[31mbold red\x1b[0m")).toBe(8);
25
- });
26
-
27
16
  it("ignores OSC 8 hyperlink sequences", () => {
28
- expect(visibleWidth("\x1b]8;;http://example.com\x07link\x1b]8;;\x07")).toBe(4);
17
+ expect(visibleWidth("\x1b]8;;http://example.com\x1b\\link\x1b]8;;\x1b\\")).toBe(4);
29
18
  });
30
19
 
31
- it("counts CJK characters as double-width", () => {
20
+ it("counts CJK and emoji as wide glyphs", () => {
32
21
  expect(visibleWidth("你好")).toBe(4);
33
- });
34
-
35
- it("handles mixed ASCII and CJK", () => {
36
- expect(visibleWidth("hi你好")).toBe(6);
37
- });
38
-
39
- it("counts emoji as double-width", () => {
40
22
  expect(visibleWidth("👋")).toBe(2);
41
23
  });
42
24
 
43
25
  it("counts tabs as 3 spaces", () => {
44
26
  expect(visibleWidth("\t")).toBe(3);
45
27
  });
46
-
47
- it("counts skin tone modifiers as single glyph", () => {
48
- expect(visibleWidth("👋🏽")).toBe(2);
49
- });
50
-
51
- it("handles string with only ANSI codes", () => {
52
- expect(visibleWidth("\x1b[31m\x1b[0m")).toBe(0);
53
- });
54
-
55
- it("handles multiple emoji in a row", () => {
56
- const w = visibleWidth("🔥🎉✅");
57
- expect(w).toBeGreaterThanOrEqual(4); // at least 2 wide emoji
58
- });
59
-
60
- // ── OSC sequence handling ────────────────────────────────────────────
61
-
62
- it("strips terminated OSC 1337 SetUserVar (BEL)", () => {
63
- expect(visibleWidth("\x1b]1337;SetUserVar=pi_status=ZG9uZQ==\x07(pass)")).toBe(6);
64
- });
65
-
66
- it("strips terminated OSC 1337 SetUserVar (ST)", () => {
67
- expect(visibleWidth("\x1b]1337;SetUserVar=pi_status=ZG9uZQ==\x1b\\(pass)")).toBe(6);
68
- });
69
-
70
- it("strips unterminated OSC without crashing (may over-strip)", () => {
71
- // Without a terminator, visible text after the OSC body is ambiguous.
72
- // The regex consumes everything until the next ESC or end-of-string.
73
- // Over-stripping is acceptable — crashing is not.
74
- const width = visibleWidth("\x1b]1337;SetUserVar=pi_status=ZG9uZQ==(pass)");
75
- expect(width).toBeLessThanOrEqual(6);
76
- });
77
-
78
- it("strips chained unterminated OSC sequences without crashing", () => {
79
- // Each unterminated OSC consumes up to the next ESC. The last one
80
- // consumes to end-of-string, eating any trailing visible text.
81
- const line =
82
- "\x1b]1337;SetUserVar=pi_status=" +
83
- "\x1b]1337;SetUserVar=pi_status=d29ya2luZw==" +
84
- "\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
85
- "(pass)";
86
- expect(visibleWidth(line)).toBeLessThanOrEqual(6);
87
- });
88
-
89
- it("unterminated OSC stops at next ESC (preserves text after next escape)", () => {
90
- // Unterminated OSC consumes to next ESC, but text AFTER the next CSI is preserved.
91
- const line =
92
- "\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
93
- "\x1b[39m" + // CSI resets color — next ESC stops the unterminated OSC
94
- "visible";
95
- expect(visibleWidth(line)).toBe(7);
96
- });
97
-
98
- it("handles real crash pattern: chained unterminated OSC between CSI sequences", () => {
99
- // Reproduces the actual crash: CSI color + unterminated OSC chain + CSI reset
100
- const line =
101
- "\x1b[48;2;10;10;20m" +
102
- "\x1b[38;2;152;152;152m" +
103
- "\x1b]1337;SetUserVar=pi_status=" +
104
- "\x1b]1337;SetUserVar=pi_status=d29ya2luZw==" +
105
- "\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
106
- "\x1b]1337;SetUserVar=pi_status=d29ya2luZw==" +
107
- "\x1b]1337;SetUserVar=pi_status=ZG9uZQ==" +
108
- "(pass)" +
109
- "\x1b[49m\x1b[0m";
110
- // Must not exceed any reasonable terminal width — the original crash was 182
111
- expect(visibleWidth(line)).toBeLessThan(20);
112
- });
113
-
114
- it("strips unterminated APC sequences without crashing", () => {
115
- const width = visibleWidth("\x1b_some-apc-data(pass)");
116
- expect(width).toBeLessThanOrEqual(6);
117
- });
118
-
119
- it("strips arbitrary unterminated OSC numbers", () => {
120
- const width = visibleWidth("\x1b]999;custom=datavisible");
121
- expect(width).toBeLessThanOrEqual(7);
122
- });
123
28
  });
124
29
 
125
- // ── truncateToWidth ──────────────────────────────────────────────────────────
126
-
127
30
  describe("truncateToWidth", () => {
128
- it("returns string unchanged when within width", () => {
31
+ it("returns text unchanged when it fits", () => {
129
32
  expect(truncateToWidth("hello", 10, "…")).toBe("hello");
130
33
  });
131
34
 
132
- it("truncates and appends ellipsis", () => {
35
+ it("truncates long text to the target width", () => {
133
36
  const result = truncateToWidth("hello world", 8, "…");
134
37
  expect(visibleWidth(result)).toBeLessThanOrEqual(8);
135
38
  expect(result).toContain("…");
136
39
  });
137
40
 
138
- it("handles CJK truncation at character boundary", () => {
139
- const result = truncateToWidth("你好世界", 5, "…");
140
- expect(visibleWidth(result)).toBeLessThanOrEqual(5);
141
- });
142
-
143
- it("preserves ANSI codes in truncated output", () => {
41
+ it("preserves ANSI sequences in truncated output", () => {
144
42
  const result = truncateToWidth("\x1b[31mhello world\x1b[0m", 8, "…");
145
43
  expect(result).toContain("\x1b[31m");
146
- });
147
-
148
- it("handles exact width match", () => {
149
- const result = truncateToWidth("hello", 5, "…");
150
- expect(result).toBe("hello");
151
- });
152
-
153
- it("handles width of 1 with ellipsis", () => {
154
- const result = truncateToWidth("hello", 1, "…");
155
- expect(visibleWidth(result)).toBeLessThanOrEqual(1);
44
+ expect(visibleWidth(result)).toBeLessThanOrEqual(8);
156
45
  });
157
46
 
158
47
  it("handles OSC 8 hyperlinks", () => {
159
- const linked = "\x1b]8;;file:///test\x07long-filename.ts\x1b]8;;\x07";
48
+ const linked = "\x1b]8;;file:///test\x1blong-filename.ts\x1b]8;;\x1b\\";
160
49
  const result = truncateToWidth(linked, 10, "…");
161
50
  expect(visibleWidth(result)).toBeLessThanOrEqual(10);
162
51
  });
163
-
164
- it("preserves ANSI styling through ellipsis", () => {
165
- const red = "\x1b[31m";
166
- const reset = "\x1b[0m";
167
- const styled = `${red}${"a".repeat(20)}${reset}`;
168
- const result = truncateToWidth(styled, 10, "…");
169
- // Ellipsis should appear BEFORE reset so it inherits the line's styling
170
- expect(result).toContain("…\x1b[0m");
171
- expect(result).not.toContain("\x1b[0m…");
172
- });
173
-
174
- it("places reset after ellipsis for unstyled text too", () => {
175
- const result = truncateToWidth("a".repeat(20), 10, "…");
176
- // Even for unstyled text, the reset-after-ellipsis order is correct
177
- expect(result).toContain("…\x1b[0m");
178
- });
179
52
  });
180
53
 
181
- // ── wrapTextWithAnsi ─────────────────────────────────────────────────────────
182
-
183
54
  describe("wrapTextWithAnsi", () => {
184
55
  it("wraps plain text at word boundaries", () => {
185
56
  const lines = wrapTextWithAnsi("hello world foo", 10);
@@ -194,66 +65,10 @@ describe("wrapTextWithAnsi", () => {
194
65
  expect(lines.length).toBeGreaterThan(1);
195
66
  });
196
67
 
197
- it("handles long words that exceed width", () => {
198
- const lines = wrapTextWithAnsi("superlongword", 5);
199
- for (const line of lines) {
68
+ it("handles long words and empty strings", () => {
69
+ for (const line of wrapTextWithAnsi("superlongword", 5)) {
200
70
  expect(visibleWidth(line)).toBeLessThanOrEqual(5);
201
71
  }
202
- });
203
-
204
- it("handles empty string", () => {
205
72
  expect(wrapTextWithAnsi("", 80)).toEqual([""]);
206
73
  });
207
-
208
- it("handles CJK wrapping", () => {
209
- const lines = wrapTextWithAnsi("你好世界测试", 5);
210
- for (const line of lines) {
211
- expect(visibleWidth(line)).toBeLessThanOrEqual(5);
212
- }
213
- });
214
-
215
- it("does not wrap when text fits", () => {
216
- const lines = wrapTextWithAnsi("short", 80);
217
- expect(lines).toHaveLength(1);
218
- expect(lines[0]).toBe("short");
219
- });
220
- });
221
-
222
- // ── hyperlink ────────────────────────────────────────────────────────────────
223
-
224
- describe("hyperlink", () => {
225
- it("wraps text in OSC 8 sequences", () => {
226
- const result = hyperlink("https://example.com", "click");
227
- expect(result).toBe("\x1b]8;;https://example.com\x07click\x1b]8;;\x07");
228
- });
229
-
230
- it("has zero visible width overhead", () => {
231
- expect(visibleWidth(hyperlink("https://x.com", "text"))).toBe(4);
232
- });
233
-
234
- it("handles empty text", () => {
235
- const result = hyperlink("https://x.com", "");
236
- expect(visibleWidth(result)).toBe(0);
237
- });
238
- });
239
-
240
- // ── fileLink ─────────────────────────────────────────────────────────────────
241
-
242
- describe("fileLink", () => {
243
- it("creates file:// URL from path", () => {
244
- const result = fileLink("/path/to/file.ts");
245
- expect(result).toContain("file:///path/to/file.ts");
246
- expect(visibleWidth(result)).toBe("/path/to/file.ts".length);
247
- });
248
-
249
- it("percent-encodes spaces in path", () => {
250
- const result = fileLink("/path/with spaces/file.ts");
251
- expect(result).toContain("file:///path/with%20spaces/file.ts");
252
- expect(visibleWidth(result)).toBe("/path/with spaces/file.ts".length);
253
- });
254
-
255
- it("uses custom display text", () => {
256
- const result = fileLink("/long/path/file.ts", "file.ts");
257
- expect(visibleWidth(result)).toBe(7);
258
- });
259
74
  });