@selesai/code 0.9.15 → 0.10.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.
Files changed (267) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/package.json +3 -3
  193. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  194. package/dist/extensions/auto-model/classifier.ts +0 -23
  195. package/dist/extensions/auto-model/config.ts +0 -51
  196. package/dist/extensions/auto-model/index.ts +0 -22
  197. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  198. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  199. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  200. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  201. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  202. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  203. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  204. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  205. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  206. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  207. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  208. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  209. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  210. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  211. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  212. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  213. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  214. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  215. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  216. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  217. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  218. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  219. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  220. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  221. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  222. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  223. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  224. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  225. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  226. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  227. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  228. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  229. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  230. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  231. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  232. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  233. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  234. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  235. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  236. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  237. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  238. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  239. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  240. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  241. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  242. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  243. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  244. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  245. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  246. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  247. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  248. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  249. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  250. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  251. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  252. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  253. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  254. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  255. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  256. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  257. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  258. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  259. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  260. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  261. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  262. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  263. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  264. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  265. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  266. package/dist/extensions/preview-tools-disabled.ts +0 -11
  267. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,811 @@
1
+ import { homedir } from "node:os";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
+ import { visibleWidth } from "@earendil-works/pi-tui";
4
+ import { describe, expect, it } from "vitest";
5
+ import { defaultConfig, mergeConfig, type PolishedTuiConfig } from "../extensions/zentui/config";
6
+ import { installFooter } from "../extensions/zentui/footer";
7
+ import { emptyGitStatus } from "../extensions/zentui/git";
8
+ import {
9
+ formatElapsedDuration,
10
+ type MinimalistEditorMetadata,
11
+ renderMinimalistFrame,
12
+ } from "../extensions/zentui/minimalist-editor";
13
+ import { createInitialState } from "../extensions/zentui/state";
14
+
15
+ function theme(): Theme {
16
+ return {
17
+ fg(_color: string, text: string) {
18
+ return text;
19
+ },
20
+ bold: (text: string) => text,
21
+ italic: (text: string) => text,
22
+ underline: (text: string) => text,
23
+ strikethrough: (text: string) => text,
24
+ inverse: (text: string) => text,
25
+ } as Theme;
26
+ }
27
+
28
+ function recordingTheme(calls: Array<{ color: string; text: string }>): Theme {
29
+ return {
30
+ ...theme(),
31
+ fg(color: string, text: string) {
32
+ calls.push({ color, text });
33
+ return text;
34
+ },
35
+ } as Theme;
36
+ }
37
+
38
+ function config(overrides: Partial<PolishedTuiConfig> = {}): PolishedTuiConfig {
39
+ const editor = defaultConfig.components.editor;
40
+ return {
41
+ ...defaultConfig,
42
+ ...overrides,
43
+ icons: { ...defaultConfig.icons, mode: "nerd" },
44
+ components: {
45
+ ...defaultConfig.components,
46
+ editor: {
47
+ ...editor,
48
+ enabled: true,
49
+ style: "minimalist",
50
+ borderColorMode: overrides.editorBorderColorMode ?? "static",
51
+ colorSource: overrides.colorSources?.editor ?? editor.colorSource,
52
+ styles: {
53
+ ...editor.styles,
54
+ minimalist: {
55
+ ...editor.styles.minimalist,
56
+ contextGauge: false,
57
+ ...overrides.editorStyles?.minimalist,
58
+ },
59
+ },
60
+ },
61
+ },
62
+ };
63
+ }
64
+
65
+ function render(width = 80, inputText = "draft", viewport?: { above?: string; below?: string }) {
66
+ return renderMinimalistFrame({
67
+ width,
68
+ editorLines: ["draft"],
69
+ autocompleteLines: ["suggestion"],
70
+ viewport,
71
+ inputText,
72
+ metadata: {
73
+ cwd: `${homedir()}/project`,
74
+ branch: "feature/minimalist",
75
+ dirty: true,
76
+ ahead: 2,
77
+ behind: 1,
78
+ costLabel: "$0.123",
79
+ modelLabel: "model-x",
80
+ thinkingLevel: "high",
81
+ contextPercent: 42.4,
82
+ sessionName: "release prep",
83
+ agentDurationMs: 12_500,
84
+ agentActive: true,
85
+ },
86
+ uiTheme: theme(),
87
+ config: config(),
88
+ });
89
+ }
90
+
91
+ describe("minimalist editor frame", () => {
92
+ it("renders metadata and framed autocomplete", () => {
93
+ const lines = render();
94
+ expect(lines[0]).toContain("12s · release prep");
95
+ expect(lines[0]).toContain("$0.123 – model-x – high – 42%");
96
+ expect(lines[0]).toMatch(/^╭.*╮$/);
97
+ expect(lines[1]).toMatch(/^│ draft\s+│$/);
98
+ expect(lines[2]).toMatch(/^├─+┤$/);
99
+ expect(lines[3]).toContain("suggestion");
100
+ expect(lines.at(-1)).toContain("feature/minimalist * ↑2 ↓1");
101
+ expect(lines.at(-1)).toContain("project");
102
+ expect(lines.at(-1)).toMatch(/^╰.*╯$/);
103
+ });
104
+
105
+ it("uses distinct theme roles for default minimalist metadata", () => {
106
+ const calls: Array<{ color: string; text: string }> = [];
107
+ const lines = renderMinimalistFrame({
108
+ width: 120,
109
+ editorLines: ["draft"],
110
+ inputText: "draft",
111
+ metadata: {
112
+ cwd: "/tmp/project",
113
+ branch: "main",
114
+ costLabel: "$0.123",
115
+ modelLabel: "model-x",
116
+ thinkingLevel: "high",
117
+ contextPercent: 42,
118
+ },
119
+ uiTheme: recordingTheme(calls),
120
+ config: config(),
121
+ });
122
+
123
+ expect(lines.join("\n")).toContain("main");
124
+ expect(calls).toEqual(
125
+ expect.arrayContaining([
126
+ { color: "success", text: "$0.123" },
127
+ { color: "syntaxKeyword", text: "model-x" },
128
+ { color: "warning", text: "high" },
129
+ { color: "muted", text: "42%" },
130
+ { color: "syntaxKeyword", text: "main" },
131
+ { color: "syntaxFunction", text: "project" },
132
+ ]),
133
+ );
134
+ const branchColor = calls.find(({ text }) => text === "main")?.color;
135
+ const cwdColor = calls.find(({ text }) => text === "project")?.color;
136
+ expect(branchColor).toBe("syntaxKeyword");
137
+ expect(branchColor).not.toBe(cwdColor);
138
+ const chromeCalls = calls.filter(({ text }) => /^[╭╮╰╯─│ –]+$/.test(text));
139
+ expect(chromeCalls.length).toBeGreaterThan(0);
140
+ expect(new Set(chromeCalls.map(({ color }) => color))).toEqual(new Set(["borderMuted"]));
141
+ });
142
+
143
+ it.each([
144
+ ["bold purple", "syntaxKeyword"],
145
+ ["success", "success"],
146
+ ] as const)("preserves an explicit legacy gitBranch color %s", (gitBranch, expectedColor) => {
147
+ const calls: Array<{ color: string; text: string }> = [];
148
+ renderMinimalistFrame({
149
+ width: 80,
150
+ editorLines: ["draft"],
151
+ inputText: "draft",
152
+ metadata: { cwd: "", branch: "main" },
153
+ uiTheme: recordingTheme(calls),
154
+ config: config({ colors: mergeConfig({ colors: { gitBranch } }).colors }),
155
+ });
156
+ expect(calls).toContainEqual({ color: expectedColor, text: "main" });
157
+ });
158
+
159
+ it.each([
160
+ [70, "warning"],
161
+ [90, "error"],
162
+ ] as const)("keeps %i%% context on the %s semantic tier", (contextPercent, color) => {
163
+ const calls: Array<{ color: string; text: string }> = [];
164
+ renderMinimalistFrame({
165
+ width: 80,
166
+ editorLines: ["draft"],
167
+ inputText: "draft",
168
+ metadata: { cwd: "", contextPercent },
169
+ uiTheme: recordingTheme(calls),
170
+ config: config(),
171
+ });
172
+ expect(calls).toContainEqual({ color, text: `${contextPercent}%` });
173
+ });
174
+
175
+ it("preserves custom theme roles", () => {
176
+ const calls: Array<{ color: string; text: string }> = [];
177
+ const colors = {
178
+ ...defaultConfig.colors,
179
+ cwd: "accent",
180
+ gitBranch: "bold purple",
181
+ editorGitBranch: "success",
182
+ cost: "warning",
183
+ contextNormal: "dim",
184
+ editorModel: "text",
185
+ editorThinkingHigh: "thinkingHigh",
186
+ };
187
+ renderMinimalistFrame({
188
+ width: 120,
189
+ editorLines: ["draft"],
190
+ inputText: "draft",
191
+ metadata: {
192
+ cwd: "/tmp/project",
193
+ branch: "main",
194
+ costLabel: "$0.123",
195
+ modelLabel: "model-x",
196
+ thinkingLevel: "high",
197
+ contextPercent: 42,
198
+ },
199
+ uiTheme: recordingTheme(calls),
200
+ config: config({ colors }),
201
+ });
202
+ expect(calls).toEqual(
203
+ expect.arrayContaining([
204
+ { color: "warning", text: "$0.123" },
205
+ { color: "text", text: "model-x" },
206
+ { color: "thinkingHigh", text: "high" },
207
+ { color: "muted", text: "42%" },
208
+ { color: "success", text: "main" },
209
+ { color: "accent", text: "project" },
210
+ ]),
211
+ );
212
+ });
213
+
214
+ it("uses the complete default terminal palette", () => {
215
+ const terminalConfig = config({
216
+ colorSources: { ...defaultConfig.colorSources, editor: "terminal" },
217
+ });
218
+ const output = renderMinimalistFrame({
219
+ width: 120,
220
+ editorLines: ["draft"],
221
+ inputText: "draft",
222
+ metadata: {
223
+ cwd: "/tmp/project",
224
+ branch: "main",
225
+ costLabel: "$0.123",
226
+ modelLabel: "model-x",
227
+ thinkingLevel: "high",
228
+ contextPercent: 42,
229
+ },
230
+ uiTheme: theme(),
231
+ config: terminalConfig,
232
+ }).join("\n");
233
+
234
+ expect(output).toContain("\x1b[1;32m$0.123\x1b[0m");
235
+ expect(output).toContain("\x1b[1;35mmodel-x\x1b[0m");
236
+ expect(output).toContain("\x1b[1;33mhigh\x1b[0m");
237
+ expect(output).toContain("\x1b[90m42%\x1b[0m");
238
+ expect(output).toContain("\x1b[1;34mmain\x1b[0m");
239
+ expect(output).toContain("\x1b[1;36mproject\x1b[0m");
240
+ expect(output).toContain("\x1b[90m╭\x1b[0m");
241
+
242
+ for (const [contextPercent, expected] of [
243
+ [70, "\x1b[1;33m70%\x1b[0m"],
244
+ [90, "\x1b[1;31m90%\x1b[0m"],
245
+ ] as const) {
246
+ const contextOutput = renderMinimalistFrame({
247
+ width: 80,
248
+ editorLines: ["draft"],
249
+ inputText: "draft",
250
+ metadata: { cwd: "", contextPercent },
251
+ uiTheme: theme(),
252
+ config: terminalConfig,
253
+ }).join("\n");
254
+ expect(contextOutput).toContain(expected);
255
+ }
256
+ });
257
+
258
+ it("preserves custom terminal colors and legacy branch provenance", () => {
259
+ const legacyColors = mergeConfig({
260
+ colors: {
261
+ gitBranch: "cyan",
262
+ editorModel: "bright-purple",
263
+ editorThinkingHigh: "yellow",
264
+ },
265
+ }).colors;
266
+ const legacyOutput = renderMinimalistFrame({
267
+ width: 120,
268
+ editorLines: ["draft"],
269
+ inputText: "draft",
270
+ metadata: {
271
+ cwd: "",
272
+ branch: "main",
273
+ modelLabel: "model-x",
274
+ thinkingLevel: "high",
275
+ },
276
+ uiTheme: theme(),
277
+ config: config({
278
+ colorSources: { ...defaultConfig.colorSources, editor: "terminal" },
279
+ colors: legacyColors,
280
+ }),
281
+ }).join("\n");
282
+ expect(legacyOutput).toContain("\x1b[95mmodel-x\x1b[0m");
283
+ expect(legacyOutput).toContain("\x1b[33mhigh\x1b[0m");
284
+ expect(legacyOutput).toContain("\x1b[36mmain\x1b[0m");
285
+
286
+ const canonicalOutput = renderMinimalistFrame({
287
+ width: 80,
288
+ editorLines: ["draft"],
289
+ inputText: "draft",
290
+ metadata: { cwd: "", branch: "main" },
291
+ uiTheme: theme(),
292
+ config: config({
293
+ colorSources: { ...defaultConfig.colorSources, editor: "terminal" },
294
+ colors: mergeConfig({
295
+ colors: { gitBranch: "cyan", editorGitBranch: "bright-green" },
296
+ }).colors,
297
+ }),
298
+ }).join("\n");
299
+ expect(canonicalOutput).toContain("\x1b[92mmain\x1b[0m");
300
+ });
301
+
302
+ it("puts complete viewport counts first on their matching borders", () => {
303
+ const lines = render(80, "draft", { above: "7", below: "11" });
304
+ expect(lines[0]).toMatch(/^╭─ ↑ 7 more · 12s · release prep/);
305
+ expect(lines.at(-1)).toMatch(/^╰─ ↓ 11 more · feature\/minimalist \* ↑2 ↓1/);
306
+ });
307
+
308
+ it.each([
309
+ [{ above: "7" }, "↑ 7 more", "↓"],
310
+ [{ below: "11" }, "↓ 11 more", "↑"],
311
+ ] as const)("supports one-sided viewport counts", (viewport, present, absent) => {
312
+ const borders = [render(80, "draft", viewport)[0], render(80, "draft", viewport).at(-1)].join(
313
+ "\n",
314
+ );
315
+ expect(borders).toContain(present);
316
+ expect(borders).not.toContain(`${absent} `);
317
+ });
318
+
319
+ it("omits viewport counts atomically when they do not fit", () => {
320
+ const lines = renderMinimalistFrame({
321
+ width: 18,
322
+ editorLines: ["draft"],
323
+ viewport: { above: "123456789", below: "987654321" },
324
+ inputText: "draft",
325
+ metadata: { cwd: "" },
326
+ uiTheme: theme(),
327
+ config: config(),
328
+ });
329
+ expect(lines[0]).not.toContain("more");
330
+ expect(lines.at(-1)).not.toContain("more");
331
+ expect(lines.every((line) => visibleWidth(line) <= 18)).toBe(true);
332
+ });
333
+
334
+ it("shows visual shell markers without changing editor text", () => {
335
+ expect(render(80, " !pwd")[0]).toContain("$ · 12s");
336
+ expect(render(80, " !!pwd")[0]).toContain("$ · 12s");
337
+ expect(render(80, "draft")[0]).not.toContain("$ · 12s");
338
+ expect(render(80, " !pwd")[1]).toContain("draft");
339
+ });
340
+
341
+ it("shows the explicit session name after the timer and omits it when disabled", () => {
342
+ const enabled = renderMinimalistFrame({
343
+ width: 80,
344
+ editorLines: ["draft"],
345
+ inputText: "draft",
346
+ metadata: { cwd: "/tmp", agentDurationMs: 12_000, sessionName: "release\nprep" },
347
+ uiTheme: theme(),
348
+ config: config(),
349
+ })[0];
350
+ expect(enabled).toContain("12s · release prep");
351
+
352
+ const disabled = renderMinimalistFrame({
353
+ width: 80,
354
+ editorLines: ["draft"],
355
+ inputText: "draft",
356
+ metadata: { cwd: "/tmp", agentDurationMs: 12_000, sessionName: "release prep" },
357
+ uiTheme: theme(),
358
+ config: config({
359
+ editorStyles: {
360
+ minimalist: {
361
+ ...defaultConfig.editorStyles.minimalist,
362
+ showSessionName: false,
363
+ },
364
+ },
365
+ }),
366
+ })[0];
367
+ expect(disabled).not.toContain("release prep");
368
+ });
369
+
370
+ it("drops a long session name before viewport and operational indicators", () => {
371
+ const top = renderMinimalistFrame({
372
+ width: 34,
373
+ editorLines: ["draft"],
374
+ viewport: { above: "7" },
375
+ inputText: "!pwd",
376
+ metadata: {
377
+ cwd: "/tmp",
378
+ agentDurationMs: 12_000,
379
+ sessionName: "a very long session name that cannot fit",
380
+ },
381
+ uiTheme: theme(),
382
+ config: config(),
383
+ })[0];
384
+ expect(top).toContain("↑ 7 more · $ · 12s");
385
+ expect(top).not.toContain("session");
386
+ expect(visibleWidth(top)).toBeLessThanOrEqual(34);
387
+ });
388
+
389
+ it("formats active and completed elapsed durations", () => {
390
+ expect(formatElapsedDuration(-1)).toBe("0s");
391
+ expect(formatElapsedDuration(65_999)).toBe("1m 5s");
392
+ expect(formatElapsedDuration(3_661_000)).toBe("1h 1m");
393
+ });
394
+
395
+ it.each([5, 6, 8, 12, 20, 40, 80])("keeps every decorated row within %i columns", (width) => {
396
+ const lines = render(width);
397
+ expect(lines.every((line) => visibleWidth(line) <= width)).toBe(true);
398
+ expect(lines[0]).toMatch(/^╭.*╮$/);
399
+ expect(lines.at(-1)).toMatch(/^╰.*╯$/);
400
+ });
401
+
402
+ it("fits long Git and cwd labels with the shared balanced border rule", () => {
403
+ const lines = renderMinimalistFrame({
404
+ width: 32,
405
+ editorLines: ["draft"],
406
+ inputText: "draft",
407
+ metadata: {
408
+ cwd: "/a/very/long/project/path/that/does/not/fit",
409
+ branch: "feature/a-very-long-branch-name-that-does-not-fit",
410
+ dirty: true,
411
+ },
412
+ uiTheme: theme(),
413
+ config: config(),
414
+ });
415
+ expect(lines.every((line) => visibleWidth(line) <= 32)).toBe(true);
416
+ expect(lines.at(-1)).toMatch(/^╰.*╯$/);
417
+ expect(lines.at(-1)).toContain("…");
418
+ });
419
+
420
+ it("renders Unicode and ANSI content without overflowing", () => {
421
+ const lines = renderMinimalistFrame({
422
+ width: 18,
423
+ editorLines: ["界 e\u0301 👩‍💻 \x1b[31mred\x1b[0m"],
424
+ inputText: "界",
425
+ metadata: {
426
+ cwd: "/tmp/界-project",
427
+ branch: "feature/👩‍💻-e\u0301-long",
428
+ modelLabel: "模型-👩‍💻",
429
+ contextPercent: 99,
430
+ },
431
+ uiTheme: theme(),
432
+ config: config(),
433
+ });
434
+ expect(lines.every((line) => visibleWidth(line) <= 18)).toBe(true);
435
+ expect(lines.join("\n")).toContain("界");
436
+ });
437
+
438
+ it("renders compact, project-relative, and full minimalist paths", () => {
439
+ const pathLine = (pathDisplay: "compact" | "project" | "full", projectRoot?: string) =>
440
+ renderMinimalistFrame({
441
+ width: 80,
442
+ editorLines: ["draft"],
443
+ inputText: "draft",
444
+ metadata: { cwd: `${homedir()}/workspace/repo/src/lib`, projectRoot },
445
+ uiTheme: theme(),
446
+ config: config({
447
+ editorStyles: {
448
+ minimalist: { ...defaultConfig.editorStyles.minimalist, pathDisplay },
449
+ },
450
+ }),
451
+ }).at(-1) ?? "";
452
+
453
+ expect(pathLine("compact")).toContain("lib");
454
+ expect(pathLine("project", `${homedir()}/workspace/repo`)).toContain("repo/src/lib");
455
+ expect(pathLine("project")).toContain("~/workspace/repo/src/lib");
456
+ expect(pathLine("full")).toContain("~/workspace/repo/src/lib");
457
+ });
458
+
459
+ it("supports focused visibility controls", () => {
460
+ const lines = renderMinimalistFrame({
461
+ width: 80,
462
+ editorLines: ["draft"],
463
+ inputText: "draft",
464
+ metadata: {
465
+ cwd: "/tmp/project",
466
+ branch: "main",
467
+ costLabel: "$1.000",
468
+ agentDurationMs: 5000,
469
+ contextPercent: 11,
470
+ },
471
+ uiTheme: theme(),
472
+ config: config({
473
+ editorStyles: {
474
+ minimalist: {
475
+ ...defaultConfig.editorStyles.minimalist,
476
+ showTimer: false,
477
+ showCost: false,
478
+ showGit: false,
479
+ },
480
+ },
481
+ }),
482
+ });
483
+ const rendered = lines.join("\n");
484
+ expect(rendered).not.toContain("5s");
485
+ expect(rendered).not.toContain("$1.000");
486
+ expect(rendered).not.toContain("main");
487
+ expect(rendered).toContain("11%");
488
+ });
489
+
490
+ it("renders percent, percent-total, and gauge context forms", () => {
491
+ const contextLine = (
492
+ contextFormat: "percent" | "percent-total",
493
+ contextGauge: boolean,
494
+ contextWindow?: number,
495
+ width = 80,
496
+ ) =>
497
+ renderMinimalistFrame({
498
+ width,
499
+ editorLines: ["draft"],
500
+ inputText: "draft",
501
+ metadata: { cwd: "/tmp", contextPercent: 11, contextWindow },
502
+ uiTheme: theme(),
503
+ config: config({
504
+ editorStyles: {
505
+ minimalist: {
506
+ ...defaultConfig.editorStyles.minimalist,
507
+ contextFormat,
508
+ contextGauge,
509
+ },
510
+ },
511
+ }),
512
+ })[0] ?? "";
513
+
514
+ expect(contextLine("percent", false, 372_000)).toContain("11%");
515
+ expect(contextLine("percent", false, 372_000)).not.toContain("372k");
516
+ expect(contextLine("percent-total", false, 372_000)).toContain("11%/372k");
517
+ expect(contextLine("percent-total", false)).toContain("11%");
518
+ expect(contextLine("percent-total", false)).not.toContain("/");
519
+ expect(contextLine("percent-total", true, 372_000)).toContain("[█░░░░] 11%/372k");
520
+ const narrow = contextLine("percent-total", true, 372_000, 16);
521
+ expect(narrow).toContain("11%/372k");
522
+ expect(narrow).not.toContain("[");
523
+ expect(visibleWidth(narrow)).toBeLessThanOrEqual(16);
524
+ });
525
+
526
+ it("uses minimalist-owned context thresholds independently of footer thresholds", () => {
527
+ const base = config();
528
+ const divergent = {
529
+ ...base,
530
+ colors: {
531
+ ...base.colors,
532
+ contextNormal: "bright-black",
533
+ contextWarning: "yellow",
534
+ contextError: "red",
535
+ },
536
+ components: {
537
+ ...base.components,
538
+ editor: {
539
+ ...base.components.editor,
540
+ colorSource: "terminal" as const,
541
+ styles: {
542
+ ...base.components.editor.styles,
543
+ minimalist: {
544
+ ...base.components.editor.styles.minimalist,
545
+ contextThresholds: { warning: 40, error: 60 },
546
+ },
547
+ },
548
+ },
549
+ footer: {
550
+ ...base.components.footer,
551
+ colorSource: "terminal" as const,
552
+ styles: {
553
+ starship: {
554
+ ...base.components.footer.styles.starship,
555
+ format: "$context",
556
+ responsive: false,
557
+ contextThresholds: { warning: 70, error: 90 },
558
+ },
559
+ },
560
+ },
561
+ },
562
+ };
563
+ const taggedTheme = {
564
+ ...theme(),
565
+ fg(color: string, text: string) {
566
+ return `[${color}]${text}`;
567
+ },
568
+ } as Theme;
569
+ const rendered = renderMinimalistFrame({
570
+ width: 80,
571
+ editorLines: ["draft"],
572
+ inputText: "draft",
573
+ metadata: { cwd: "/tmp", contextPercent: 50 },
574
+ uiTheme: taggedTheme,
575
+ config: divergent,
576
+ }).join("\n");
577
+ expect(rendered).toContain("\x1b[33m50%\x1b[0m");
578
+ expect(rendered).not.toContain("\x1b[90m50%\x1b[0m");
579
+
580
+ let footerFactory:
581
+ | ((
582
+ tui: { requestRender(): void },
583
+ theme: Theme,
584
+ data: {
585
+ onBranchChange(callback: () => void): () => void;
586
+ getExtensionStatuses(): Map<string, string>;
587
+ },
588
+ ) => { render(width: number): string[] })
589
+ | undefined;
590
+ const footerContext = {
591
+ cwd: "/tmp",
592
+ model: { contextWindow: 100_000 },
593
+ getContextUsage: () => ({ percent: 50, tokens: 50_000, contextWindow: 100_000 }),
594
+ sessionManager: { getSessionName: () => undefined },
595
+ ui: {
596
+ setFooter(factory: typeof footerFactory) {
597
+ footerFactory = factory;
598
+ },
599
+ },
600
+ };
601
+ installFooter(footerContext as never, createInitialState(emptyGitStatus()), () => divergent, {
602
+ setRequestRender() {},
603
+ scheduleProjectRefresh() {},
604
+ });
605
+ const footer = footerFactory?.({ requestRender() {} }, taggedTheme, {
606
+ onBranchChange: () => () => {},
607
+ getExtensionStatuses: () => new Map(),
608
+ });
609
+ const footerRendered = footer?.render(80).join("\n") ?? "";
610
+ expect(footerRendered).toContain("\x1b[90m50.0%/100k\x1b[0m");
611
+ expect(footerRendered).not.toContain("\x1b[33m50.0%/100k\x1b[0m");
612
+ });
613
+
614
+ it("uses the adaptive border callback when configured", () => {
615
+ const lines = renderMinimalistFrame({
616
+ width: 20,
617
+ editorLines: ["draft"],
618
+ inputText: "!pwd",
619
+ metadata: { cwd: "/tmp" },
620
+ uiTheme: theme(),
621
+ config: config({ editorBorderColorMode: "adaptive" }),
622
+ borderColor: (text) => `\x1b[36m${text}\x1b[0m`,
623
+ });
624
+ expect(lines.join("\n")).toContain("\x1b[36m");
625
+ expect(lines.every((line) => visibleWidth(line) <= 20)).toBe(true);
626
+ });
627
+
628
+ it("keeps adaptive theme borders and thinking labels on the same renderer", () => {
629
+ const colors = {
630
+ ...defaultConfig.colors,
631
+ editorBorder: "error",
632
+ editorThinkingHigh: "success",
633
+ };
634
+ const adaptiveConfig = config({ colors, editorBorderColorMode: "adaptive" });
635
+ const renderWith = (uiTheme: Theme, borderColor?: (text: string) => string) =>
636
+ renderMinimalistFrame({
637
+ width: 80,
638
+ editorLines: ["draft"],
639
+ inputText: "draft",
640
+ metadata: { cwd: "", thinkingLevel: "high" },
641
+ uiTheme,
642
+ config: adaptiveConfig,
643
+ borderColor,
644
+ }).join("\n");
645
+
646
+ const adaptive = renderWith(theme(), (text) => `\x1b[36m${text}\x1b[0m`);
647
+ expect(adaptive).toContain("\x1b[36m╭\x1b[0m");
648
+ expect(adaptive).toContain("\x1b[36mhigh\x1b[0m");
649
+
650
+ for (const failedBorderColor of [
651
+ undefined,
652
+ () => {
653
+ throw new Error("adaptive color failed");
654
+ },
655
+ (() => 42) as unknown as (text: string) => string,
656
+ ]) {
657
+ const calls: Array<{ color: string; text: string }> = [];
658
+ renderWith(recordingTheme(calls), failedBorderColor);
659
+ expect(calls).toContainEqual({ color: "error", text: "╭" });
660
+ expect(calls).toContainEqual({ color: "error", text: "high" });
661
+ expect(calls).not.toContainEqual({ color: "success", text: "high" });
662
+ }
663
+
664
+ const staticCalls: Array<{ color: string; text: string }> = [];
665
+ renderMinimalistFrame({
666
+ width: 80,
667
+ editorLines: ["draft"],
668
+ inputText: "draft",
669
+ metadata: { cwd: "", thinkingLevel: "high" },
670
+ uiTheme: recordingTheme(staticCalls),
671
+ config: config({ colors, editorBorderColorMode: "static" }),
672
+ borderColor: (text) => `\x1b[36m${text}\x1b[0m`,
673
+ });
674
+ expect(staticCalls).toContainEqual({ color: "error", text: "╭" });
675
+ expect(staticCalls).toContainEqual({ color: "success", text: "high" });
676
+ });
677
+
678
+ it.each([
679
+ ["minimal", "\x1b[90m"],
680
+ ["low", "\x1b[34m"],
681
+ ["medium", "\x1b[36m"],
682
+ ["high", "\x1b[33m"],
683
+ ["xhigh", "\x1b[31m"],
684
+ ["max", "\x1b[91m"],
685
+ ] as const)("uses the terminal adaptive color for %s borders and labels", (level, ansi) => {
686
+ const output = renderMinimalistFrame({
687
+ width: 80,
688
+ editorLines: ["draft"],
689
+ inputText: "draft",
690
+ metadata: { cwd: "", thinkingLevel: level },
691
+ uiTheme: theme(),
692
+ config: config({
693
+ editorBorderColorMode: "adaptive",
694
+ colorSources: { ...defaultConfig.colorSources, editor: "terminal" },
695
+ }),
696
+ borderColor: (text) => `[theme]${text}`,
697
+ }).join("\n");
698
+ expect(output).toContain(`${ansi}╭\x1b[0m`);
699
+ expect(output).toContain(`${ansi}${level}\x1b[0m`);
700
+ expect(output).not.toContain("[theme]");
701
+ });
702
+
703
+ it.each([
704
+ [{ editorThinkingMax: "bright-purple" }, "max", "\x1b[95m"],
705
+ [{ editorThinkingXhigh: "bright-cyan" }, "max", "\x1b[96m"],
706
+ [{ editorThinking: "bright-green" }, "low", "\x1b[92m"],
707
+ ] as const)(
708
+ "uses configured terminal adaptive thinking colors for both border and %s label",
709
+ (colors, level, ansi) => {
710
+ const output = renderMinimalistFrame({
711
+ width: 80,
712
+ editorLines: ["draft"],
713
+ inputText: "draft",
714
+ metadata: { cwd: "", thinkingLevel: level },
715
+ uiTheme: theme(),
716
+ config: config({
717
+ colors: { ...defaultConfig.colors, ...colors },
718
+ editorBorderColorMode: "adaptive",
719
+ colorSources: { ...defaultConfig.colorSources, editor: "terminal" },
720
+ }),
721
+ }).join("\n");
722
+ expect(output).toContain(`${ansi}╭\x1b[0m`);
723
+ expect(output).toContain(`${ansi}${level}\x1b[0m`);
724
+ },
725
+ );
726
+
727
+ it.each([undefined, "", "off"])(
728
+ "keeps terminal adaptive borders static and omits an inactive thinking level %s",
729
+ (thinkingLevel) => {
730
+ const output = renderMinimalistFrame({
731
+ width: 80,
732
+ editorLines: ["draft"],
733
+ inputText: "draft",
734
+ metadata: { cwd: "", thinkingLevel },
735
+ uiTheme: theme(),
736
+ config: config({
737
+ editorBorderColorMode: "adaptive",
738
+ colorSources: { ...defaultConfig.colorSources, editor: "terminal" },
739
+ }),
740
+ borderColor: (text) => `[theme]${text}`,
741
+ }).join("\n");
742
+ expect(output).toContain("\x1b[90m╭\x1b[0m");
743
+ expect(output).not.toContain("[theme]");
744
+ expect(output).not.toContain("off");
745
+ },
746
+ );
747
+
748
+ it("colors every separator in the full top-right sequence with the resolved border color", () => {
749
+ const top = renderMinimalistFrame({
750
+ width: 100,
751
+ editorLines: ["draft"],
752
+ inputText: "draft",
753
+ metadata: {
754
+ cwd: "",
755
+ costLabel: "$0.000",
756
+ modelLabel: "gpt-5.6-terra",
757
+ thinkingLevel: "minimal",
758
+ contextPercent: 0,
759
+ },
760
+ uiTheme: theme(),
761
+ config: config({ editorBorderColorMode: "adaptive" }),
762
+ borderColor: (text) => `\x1b[36m${text}\x1b[0m`,
763
+ })[0];
764
+
765
+ expect(top).toContain(
766
+ "$0.000\x1b[36m – \x1b[0mgpt-5.6-terra\x1b[36m – \x1b[0m\x1b[36mminimal\x1b[0m\x1b[36m – \x1b[0m0%",
767
+ );
768
+ expect(top.match(/\x1b\[36m – \x1b\[0m/g)).toHaveLength(3);
769
+ expect(top).not.toContain("\x1b[36m$0.000");
770
+ expect(top).not.toContain("\x1b[36mgpt-5.6-terra");
771
+ });
772
+
773
+ it.each([
774
+ [
775
+ "without cost",
776
+ { cwd: "", modelLabel: "model", thinkingLevel: "low", contextPercent: 12 },
777
+ "model\x1b[36m – \x1b[0m\x1b[36mlow\x1b[0m\x1b[36m – \x1b[0m12%",
778
+ ],
779
+ [
780
+ "without model",
781
+ { cwd: "", costLabel: "$1", thinkingLevel: "high", contextPercent: 25 },
782
+ "$1\x1b[36m – \x1b[0m\x1b[36mhigh\x1b[0m\x1b[36m – \x1b[0m25%",
783
+ ],
784
+ [
785
+ "without thinking",
786
+ { cwd: "", costLabel: "$2", modelLabel: "model", contextPercent: 50 },
787
+ "$2\x1b[36m – \x1b[0mmodel\x1b[36m – \x1b[0m50%",
788
+ ],
789
+ [
790
+ "without context",
791
+ { cwd: "", costLabel: "$3", modelLabel: "model", thinkingLevel: "xhigh" },
792
+ "$3\x1b[36m – \x1b[0mmodel\x1b[36m – \x1b[0m\x1b[36mxhigh\x1b[0m",
793
+ ],
794
+ ] satisfies Array<[string, MinimalistEditorMetadata, string]>)(
795
+ "uses the resolved border separator %s",
796
+ (_name, metadata, expected) => {
797
+ const top = renderMinimalistFrame({
798
+ width: 100,
799
+ editorLines: ["draft"],
800
+ inputText: "draft",
801
+ metadata,
802
+ uiTheme: theme(),
803
+ config: config({ editorBorderColorMode: "adaptive" }),
804
+ borderColor: (text) => `\x1b[36m${text}\x1b[0m`,
805
+ })[0];
806
+
807
+ expect(top).toContain(expected);
808
+ expect(top).not.toContain("[muted] – ");
809
+ },
810
+ );
811
+ });