@dungle-scrubs/tallow 0.9.4 → 0.9.6

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 (195) hide show
  1. package/dist/cli.js +7 -4
  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/reset-diagnostics.d.ts +69 -0
  26. package/dist/reset-diagnostics.d.ts.map +1 -0
  27. package/dist/reset-diagnostics.js +41 -0
  28. package/dist/reset-diagnostics.js.map +1 -0
  29. package/dist/sdk.d.ts +5 -21
  30. package/dist/sdk.d.ts.map +1 -1
  31. package/dist/sdk.js +180 -149
  32. package/dist/sdk.js.map +1 -1
  33. package/dist/workspace-transition-interactive.d.ts +1 -0
  34. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  35. package/dist/workspace-transition-interactive.js +7 -17
  36. package/dist/workspace-transition-interactive.js.map +1 -1
  37. package/extensions/__integration__/audit-findings.test.ts +4 -5
  38. package/extensions/_icons/index.ts +2 -4
  39. package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
  40. package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
  41. package/extensions/_shared/image-metadata.ts +99 -0
  42. package/extensions/_shared/inline-preview.ts +1 -1
  43. package/extensions/_shared/terminal-links.ts +22 -0
  44. package/extensions/ask-user-question-tool/index.ts +0 -3
  45. package/extensions/clear/__tests__/clear.test.ts +269 -2
  46. package/extensions/command-expansion/index.ts +1 -1
  47. package/extensions/context-files/index.ts +5 -1
  48. package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
  49. package/extensions/context-fork/extension.json +1 -1
  50. package/extensions/context-fork/index.ts +32 -0
  51. package/extensions/edit-tool-enhanced/index.ts +2 -1
  52. package/extensions/hooks/index.ts +33 -11
  53. package/extensions/loop/index.ts +14 -1
  54. package/extensions/lsp/index.ts +64 -13
  55. package/extensions/lsp/package.json +2 -2
  56. package/extensions/random-spinner/index.ts +7 -642
  57. package/extensions/read-tool-enhanced/index.ts +6 -8
  58. package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
  59. package/extensions/render-stabilizer/index.ts +6 -6
  60. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
  61. package/extensions/slash-command-bridge/index.ts +14 -2
  62. package/extensions/subagent-tool/model-resolver.ts +274 -7
  63. package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
  64. package/extensions/teams-tool/tools/register-extension.ts +1 -3
  65. package/extensions/web-search-tool/index.ts +2 -1
  66. package/extensions/write-tool-enhanced/index.ts +2 -1
  67. package/node_modules/@mariozechner/pi-tui/README.md +56 -34
  68. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
  69. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
  70. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
  71. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
  72. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
  73. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
  74. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
  75. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
  76. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
  77. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
  78. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
  79. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
  80. package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
  81. package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
  82. package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
  83. package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
  84. package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
  85. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
  86. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
  87. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
  88. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
  89. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
  90. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
  91. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
  92. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
  93. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
  94. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
  95. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
  96. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
  97. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
  98. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
  99. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
  100. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
  101. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  102. package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
  103. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  104. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
  105. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  106. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
  107. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  108. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
  109. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  110. package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
  111. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  112. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
  113. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
  114. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
  115. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
  116. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
  117. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  118. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
  119. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  120. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
  121. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  122. package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
  123. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  124. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
  125. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
  126. package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
  127. package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
  128. package/node_modules/@mariozechner/pi-tui/package.json +6 -6
  129. package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
  130. package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
  131. package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
  132. package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
  133. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
  134. package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
  135. package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
  136. package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
  137. package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
  138. package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
  139. package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
  140. package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
  141. package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
  142. package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
  143. package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
  144. package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
  145. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
  146. package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
  147. package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
  148. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
  149. package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
  150. package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
  151. package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
  152. package/package.json +13 -13
  153. package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
  154. package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
  155. package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
  156. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
  157. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
  158. package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
  159. package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
  160. package/packages/tallow-tui/node_modules/marked/README.md +5 -4
  161. package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
  162. package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
  163. package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
  164. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
  165. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
  166. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
  167. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
  168. package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
  169. package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
  170. package/packages/tallow-tui/node_modules/marked/package.json +26 -34
  171. package/skills/tallow-expert/SKILL.md +1 -3
  172. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
  173. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
  174. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
  175. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
  176. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
  177. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
  178. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
  179. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
  180. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
  181. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
  182. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
  183. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
  184. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
  185. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
  186. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
  187. package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
  188. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
  189. package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
  190. package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
  191. package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
  192. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
  193. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
  194. package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
  195. package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
@@ -23,10 +23,7 @@ Line two
23
23
  Line three "
24
24
  `;
25
25
 
26
- exports[`Markdown renders heading 1`] = `
27
- "Hello World
28
- "
29
- `;
26
+ exports[`Markdown renders heading 1`] = `"Hello World "`;
30
27
 
31
28
  exports[`Markdown renders paragraph text 1`] = `
32
29
  "Some paragraph text that should be
@@ -37,8 +34,7 @@ exports[`Markdown renders code block 1`] = `
37
34
  "\`\`\`ts
38
35
  const x = 1;
39
36
  const y = 2;
40
- \`\`\`
41
- "
37
+ \`\`\` "
42
38
  `;
43
39
 
44
40
  exports[`Markdown renders unordered list 1`] = `
@@ -53,10 +49,7 @@ exports[`Markdown renders ordered list 1`] = `
53
49
  3. Third "
54
50
  `;
55
51
 
56
- exports[`Markdown renders blockquote 1`] = `
57
- "│ This is a quote
58
- "
59
- `;
52
+ exports[`Markdown renders blockquote 1`] = `"│ This is a quote "`;
60
53
 
61
54
  exports[`Markdown renders at narrow width (20) 1`] = `
62
55
  "Hello World
@@ -78,36 +71,6 @@ Below "
78
71
 
79
72
  exports[`Markdown handles empty content 1`] = `""`;
80
73
 
81
- exports[`BorderedBox renders content with default border 1`] = `
82
- "┌────────────────────────────┐
83
- │ Hello │
84
- │ World │
85
- └────────────────────────────┘"
86
- `;
87
-
88
- exports[`BorderedBox renders with title 1`] = `
89
- "┌─ My Box ───────────────────┐
90
- │ Content line │
91
- └────────────────────────────┘"
92
- `;
93
-
94
- exports[`BorderedBox renders with custom padding 1`] = `
95
- "┌────────────────────────────┐
96
- │ Padded │
97
- └────────────────────────────┘"
98
- `;
99
-
100
- exports[`BorderedBox handles empty content 1`] = `
101
- "┌────────────────────────────┐
102
- └────────────────────────────┘"
103
- `;
104
-
105
- exports[`BorderedBox handles very narrow width 1`] = `
106
- "┌───┐
107
- │ . │
108
- └───┘"
109
- `;
110
-
111
74
  exports[`TruncatedText renders short text without truncation 1`] = `"Short "`;
112
75
 
113
76
  exports[`TruncatedText truncates long text 1`] = `"This text is much..."`;
@@ -1,113 +1,103 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import { Image, type ImageTheme } from "../components/image.js";
3
- import { withCapabilityEnv } from "../test-utils/capability-env.js";
3
+ import { resetCapabilitiesCache } from "../terminal-image.js";
4
4
 
5
5
  const IDENTITY_THEME: ImageTheme = {
6
6
  fallbackColor: (text) => text,
7
7
  };
8
8
 
9
- /**
10
- * Extract Kitty `c=<columns>` param from rendered image lines.
11
- *
12
- * @param lines - Rendered TUI lines from Image.render
13
- * @returns Parsed Kitty column count, or null if sequence not present
14
- */
15
- function getKittyColumns(lines: readonly string[]): number | null {
16
- for (const line of lines) {
17
- const start = line.indexOf("\x1b_G");
18
- if (start < 0) {
19
- continue;
9
+ type CapabilityEnvOverrides = Readonly<Record<string, string | undefined>>;
10
+
11
+ function withCapabilityEnv<T>(overrides: CapabilityEnvOverrides, run: () => T): T {
12
+ const keys = [
13
+ "COLORTERM",
14
+ "GHOSTTY_RESOURCES_DIR",
15
+ "ITERM_SESSION_ID",
16
+ "KITTY_WINDOW_ID",
17
+ "TERM",
18
+ "TERM_PROGRAM",
19
+ "TMUX",
20
+ "WEZTERM_PANE",
21
+ ] as const;
22
+ const previous: Partial<Record<(typeof keys)[number], string | undefined>> = {};
23
+ for (const key of keys) {
24
+ previous[key] = process.env[key];
25
+ if (Object.hasOwn(overrides, key)) {
26
+ const value = overrides[key];
27
+ if (value === undefined) {
28
+ delete process.env[key];
29
+ } else {
30
+ process.env[key] = value;
31
+ }
32
+ } else {
33
+ delete process.env[key];
20
34
  }
21
- const match = line.slice(start).match(/c=(\d+)/);
22
- if (match) {
23
- return Number(match[1]);
35
+ }
36
+ resetCapabilitiesCache();
37
+ try {
38
+ return run();
39
+ } finally {
40
+ for (const key of keys) {
41
+ const value = previous[key];
42
+ if (value === undefined) {
43
+ delete process.env[key];
44
+ } else {
45
+ process.env[key] = value;
46
+ }
24
47
  }
48
+ resetCapabilitiesCache();
25
49
  }
26
- return null;
27
50
  }
28
51
 
29
- describe("Image component narrow-pane rendering", () => {
30
- it("applies one safe width contract for border and no-border rendering", () => {
31
- withCapabilityEnv({ TERM_PROGRAM: "kitty" }, () => {
32
- const dimensions = { widthPx: 4000, heightPx: 2500 };
33
- const cases = [
34
- { border: false, expectedColumns: 1, width: 1 },
35
- { border: false, expectedColumns: 6, width: 8 },
36
- { border: false, expectedColumns: 18, width: 20 },
37
- { border: true, expectedColumns: 1, width: 1 },
38
- { border: true, expectedColumns: 2, width: 8 },
39
- { border: true, expectedColumns: 14, width: 20 },
40
- ] as const;
41
-
42
- for (const testCase of cases) {
43
- const image = new Image(
44
- "AA==",
45
- "image/png",
46
- IDENTITY_THEME,
47
- { border: testCase.border, maxHeightCells: 200 },
48
- dimensions
49
- );
50
- const lines = image.render(testCase.width);
51
- expect(lines.length).toBeGreaterThan(0);
52
- expect(getKittyColumns(lines)).toBe(testCase.expectedColumns);
53
- }
54
- });
55
- });
52
+ function containsKittySequence(lines: readonly string[]): boolean {
53
+ return lines.some((line) => line.includes("\x1b_G"));
54
+ }
56
55
 
57
- it("renders portrait images at narrow widths with valid positive geometry", () => {
58
- withCapabilityEnv({ TERM_PROGRAM: "kitty" }, () => {
56
+ describe("Image component", () => {
57
+ it("renders kitty sequences when image support is available", () => {
58
+ withCapabilityEnv({ TERM_PROGRAM: "kitty", TMUX: undefined }, () => {
59
59
  const image = new Image(
60
60
  "AA==",
61
61
  "image/png",
62
62
  IDENTITY_THEME,
63
- { border: false, maxHeightCells: 200 },
63
+ { maxHeightCells: 200 },
64
64
  { widthPx: 1179, heightPx: 2556 }
65
65
  );
66
66
 
67
67
  for (const width of [8, 12, 20, 30]) {
68
68
  const lines = image.render(width);
69
- expect(lines.some((line) => line.includes("\x1b_G"))).toBe(true);
70
- const columns = getKittyColumns(lines);
71
- expect(columns).not.toBeNull();
72
- expect(columns).toBeGreaterThanOrEqual(1);
69
+ expect(lines.length).toBeGreaterThan(0);
70
+ expect(containsKittySequence(lines)).toBe(true);
73
71
  }
74
72
  });
75
73
  });
76
74
 
77
- it("renders landscape images in narrow panes for both border modes", () => {
78
- withCapabilityEnv({ TERM_PROGRAM: "kitty" }, () => {
79
- const dimensions = { widthPx: 2400, heightPx: 1200 };
80
- for (const border of [false, true]) {
81
- const image = new Image(
82
- "AA==",
83
- "image/png",
84
- IDENTITY_THEME,
85
- { border, maxHeightCells: 200 },
86
- dimensions
87
- );
88
- const lines = image.render(12);
89
- expect(lines.length).toBeGreaterThan(0);
90
- expect(getKittyColumns(lines)).toBeGreaterThanOrEqual(1);
91
- }
75
+ it("returns stable output for repeated renders at the same width", () => {
76
+ withCapabilityEnv({ TERM_PROGRAM: "kitty", TMUX: undefined }, () => {
77
+ const image = new Image(
78
+ "AA==",
79
+ "image/png",
80
+ IDENTITY_THEME,
81
+ { maxHeightCells: 200 },
82
+ { widthPx: 2400, heightPx: 1200 }
83
+ );
84
+ const first = image.render(12);
85
+ const second = image.render(12);
86
+ expect(second).toEqual(first);
92
87
  });
93
88
  });
94
89
 
95
- it("degrades safely for ultra-narrow pane widths", () => {
96
- withCapabilityEnv({ TERM_PROGRAM: "kitty" }, () => {
97
- for (const border of [false, true]) {
98
- const image = new Image(
99
- "AA==",
100
- "image/png",
101
- IDENTITY_THEME,
102
- { border, maxHeightCells: 200 },
103
- { widthPx: 1200, heightPx: 1800 }
104
- );
105
-
106
- expect(() => image.render(0)).not.toThrow();
107
- expect(() => image.render(1)).not.toThrow();
108
- expect(getKittyColumns(image.render(0))).toBe(1);
109
- expect(getKittyColumns(image.render(1))).toBe(1);
110
- }
90
+ it("falls back to text when image protocols are unavailable", () => {
91
+ withCapabilityEnv({ TERM: "tmux-256color", TERM_PROGRAM: "unknown", TMUX: "1" }, () => {
92
+ const image = new Image(
93
+ "AA==",
94
+ "image/png",
95
+ IDENTITY_THEME,
96
+ { filename: "example.png" },
97
+ { widthPx: 1200, heightPx: 1800 }
98
+ );
99
+ const lines = image.render(12);
100
+ expect(lines).toEqual(["[Image: example.png [image/png] 1200x1800]"]);
111
101
  });
112
102
  });
113
103
  });
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import { describe, expect, it } from "bun:test";
8
8
  import { renderSnapshot } from "../../../../test-utils/virtual-terminal.js";
9
- import { BorderedBox } from "../components/bordered-box.js";
10
9
  import { Markdown, type MarkdownTheme } from "../components/markdown.js";
11
10
  import { Text } from "../components/text.js";
12
11
  import { TruncatedText } from "../components/truncated-text.js";
@@ -120,38 +119,6 @@ describe("Markdown", () => {
120
119
  });
121
120
  });
122
121
 
123
- // ════════════════════════════════════════════════════════════════
124
- // BorderedBox
125
- // ════════════════════════════════════════════════════════════════
126
-
127
- describe("BorderedBox", () => {
128
- it("renders content with default border", () => {
129
- const box = new BorderedBox(["Hello", "World"]);
130
- expect(renderSnapshot(box, 30)).toMatchSnapshot();
131
- });
132
-
133
- it("renders with title", () => {
134
- const box = new BorderedBox(["Content line"], { title: "My Box" });
135
- expect(renderSnapshot(box, 30)).toMatchSnapshot();
136
- });
137
-
138
- it("renders with custom padding", () => {
139
- const box = new BorderedBox(["Padded"], { paddingX: 3 });
140
- expect(renderSnapshot(box, 30)).toMatchSnapshot();
141
- });
142
-
143
- it("handles empty content", () => {
144
- const box = new BorderedBox([]);
145
- expect(renderSnapshot(box, 30)).toMatchSnapshot();
146
- });
147
-
148
- it("handles very narrow width", () => {
149
- const box = new BorderedBox(["Some content"]);
150
- // At width 5, inner width = 5 - 2 - 2 = 1
151
- expect(renderSnapshot(box, 5)).toMatchSnapshot();
152
- });
153
- });
154
-
155
122
  // ════════════════════════════════════════════════════════════════
156
123
  // TruncatedText
157
124
  // ════════════════════════════════════════════════════════════════