termcast 1.3.21 β†’ 1.3.24

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 (412) hide show
  1. package/dist/ai.d.ts +104 -0
  2. package/dist/ai.d.ts.map +1 -0
  3. package/dist/ai.js +135 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/apis/browser-extension.d.ts +18 -0
  6. package/dist/apis/browser-extension.d.ts.map +1 -0
  7. package/dist/apis/browser-extension.js +14 -0
  8. package/dist/apis/browser-extension.js.map +1 -0
  9. package/dist/apis/localstorage.d.ts.map +1 -1
  10. package/dist/apis/localstorage.js +4 -7
  11. package/dist/apis/localstorage.js.map +1 -1
  12. package/dist/apis/oauth.d.ts.map +1 -1
  13. package/dist/apis/oauth.js +5 -1
  14. package/dist/apis/oauth.js.map +1 -1
  15. package/dist/apis/preferences.d.ts.map +1 -1
  16. package/dist/apis/preferences.js +38 -19
  17. package/dist/apis/preferences.js.map +1 -1
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +2 -1
  20. package/dist/build.js.map +1 -1
  21. package/dist/cache.d.ts +32 -0
  22. package/dist/cache.d.ts.map +1 -0
  23. package/dist/cache.js +205 -0
  24. package/dist/cache.js.map +1 -0
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +67 -31
  27. package/dist/cli.js.map +1 -1
  28. package/dist/clipboard.d.ts +36 -0
  29. package/dist/clipboard.d.ts.map +1 -0
  30. package/dist/clipboard.js +154 -0
  31. package/dist/clipboard.js.map +1 -0
  32. package/dist/compile.d.ts.map +1 -1
  33. package/dist/compile.js +22 -5
  34. package/dist/compile.js.map +1 -1
  35. package/dist/components/actions.d.ts.map +1 -1
  36. package/dist/components/actions.js +56 -30
  37. package/dist/components/actions.js.map +1 -1
  38. package/dist/components/detail.d.ts.map +1 -1
  39. package/dist/components/detail.js +4 -0
  40. package/dist/components/detail.js.map +1 -1
  41. package/dist/components/dropdown.d.ts.map +1 -1
  42. package/dist/components/dropdown.js +38 -15
  43. package/dist/components/dropdown.js.map +1 -1
  44. package/dist/components/extension-preferences.d.ts.map +1 -1
  45. package/dist/components/extension-preferences.js +40 -13
  46. package/dist/components/extension-preferences.js.map +1 -1
  47. package/dist/components/form/checkbox.d.ts.map +1 -1
  48. package/dist/components/form/checkbox.js +5 -3
  49. package/dist/components/form/checkbox.js.map +1 -1
  50. package/dist/components/form/date-picker.d.ts.map +1 -1
  51. package/dist/components/form/date-picker.js +5 -3
  52. package/dist/components/form/date-picker.js.map +1 -1
  53. package/dist/components/form/description.d.ts.map +1 -1
  54. package/dist/components/form/description.js +2 -2
  55. package/dist/components/form/description.js.map +1 -1
  56. package/dist/components/form/dropdown.d.ts.map +1 -1
  57. package/dist/components/form/dropdown.js +84 -80
  58. package/dist/components/form/dropdown.js.map +1 -1
  59. package/dist/components/form/file-autocomplete.d.ts +3 -6
  60. package/dist/components/form/file-autocomplete.d.ts.map +1 -1
  61. package/dist/components/form/file-autocomplete.js +61 -66
  62. package/dist/components/form/file-autocomplete.js.map +1 -1
  63. package/dist/components/form/file-picker.d.ts.map +1 -1
  64. package/dist/components/form/file-picker.js +33 -30
  65. package/dist/components/form/file-picker.js.map +1 -1
  66. package/dist/components/form/form-end.d.ts.map +1 -1
  67. package/dist/components/form/form-end.js +21 -1
  68. package/dist/components/form/form-end.js.map +1 -1
  69. package/dist/components/form/form-type-only.d.ts +174 -0
  70. package/dist/components/form/form-type-only.d.ts.map +1 -0
  71. package/dist/components/form/form-type-only.js +2 -0
  72. package/dist/components/form/form-type-only.js.map +1 -0
  73. package/dist/components/form/index.d.ts +3 -1
  74. package/dist/components/form/index.d.ts.map +1 -1
  75. package/dist/components/form/index.js +100 -28
  76. package/dist/components/form/index.js.map +1 -1
  77. package/dist/components/form/password-field.d.ts.map +1 -1
  78. package/dist/components/form/password-field.js +5 -3
  79. package/dist/components/form/password-field.js.map +1 -1
  80. package/dist/components/form/text-area.d.ts.map +1 -1
  81. package/dist/components/form/text-area.js +5 -3
  82. package/dist/components/form/text-area.js.map +1 -1
  83. package/dist/components/form/text-field.d.ts.map +1 -1
  84. package/dist/components/form/text-field.js +6 -4
  85. package/dist/components/form/text-field.js.map +1 -1
  86. package/dist/components/form/types.d.ts +5 -0
  87. package/dist/components/form/types.d.ts.map +1 -1
  88. package/dist/components/form/use-form-handling.d.ts +4 -0
  89. package/dist/components/form/use-form-handling.d.ts.map +1 -0
  90. package/dist/components/form/use-form-handling.js +37 -0
  91. package/dist/components/form/use-form-handling.js.map +1 -0
  92. package/dist/components/form/with-left-border.d.ts +2 -1
  93. package/dist/components/form/with-left-border.d.ts.map +1 -1
  94. package/dist/components/form/with-left-border.js +27 -3
  95. package/dist/components/form/with-left-border.js.map +1 -1
  96. package/dist/components/icon.d.ts +1 -0
  97. package/dist/components/icon.d.ts.map +1 -1
  98. package/dist/components/icon.js +24 -8
  99. package/dist/components/icon.js.map +1 -1
  100. package/dist/components/list.d.ts +2 -2
  101. package/dist/components/list.d.ts.map +1 -1
  102. package/dist/components/list.js +140 -62
  103. package/dist/components/list.js.map +1 -1
  104. package/dist/components/loading-bar.d.ts.map +1 -1
  105. package/dist/components/loading-bar.js +2 -2
  106. package/dist/components/loading-bar.js.map +1 -1
  107. package/dist/components/loading-text.d.ts +8 -0
  108. package/dist/components/loading-text.d.ts.map +1 -0
  109. package/dist/components/loading-text.js +58 -0
  110. package/dist/components/loading-text.js.map +1 -0
  111. package/dist/descendants.js +1 -1
  112. package/dist/descendants.js.map +1 -1
  113. package/dist/dev-ui.d.ts +7 -0
  114. package/dist/dev-ui.d.ts.map +1 -0
  115. package/dist/dev-ui.js +118 -0
  116. package/dist/dev-ui.js.map +1 -0
  117. package/dist/environment.d.ts +63 -0
  118. package/dist/environment.d.ts.map +1 -0
  119. package/dist/environment.js +189 -0
  120. package/dist/environment.js.map +1 -0
  121. package/dist/examples/datepicker.d.ts +2 -0
  122. package/dist/examples/datepicker.d.ts.map +1 -0
  123. package/dist/examples/datepicker.js +344 -0
  124. package/dist/examples/datepicker.js.map +1 -0
  125. package/dist/examples/file-autocomplete.vitest.d.ts +2 -0
  126. package/dist/examples/file-autocomplete.vitest.d.ts.map +1 -0
  127. package/dist/examples/file-autocomplete.vitest.js +223 -0
  128. package/dist/examples/file-autocomplete.vitest.js.map +1 -0
  129. package/dist/examples/form-basic-arrow-keys.vitest.d.ts +2 -0
  130. package/dist/examples/form-basic-arrow-keys.vitest.d.ts.map +1 -0
  131. package/dist/examples/form-basic-arrow-keys.vitest.js +46 -0
  132. package/dist/examples/form-basic-arrow-keys.vitest.js.map +1 -0
  133. package/dist/examples/form-basic.vitest.d.ts +2 -0
  134. package/dist/examples/form-basic.vitest.d.ts.map +1 -0
  135. package/dist/examples/form-basic.vitest.js +630 -0
  136. package/dist/examples/form-basic.vitest.js.map +1 -0
  137. package/dist/examples/form-dropdown-with-sections.d.ts +2 -0
  138. package/dist/examples/form-dropdown-with-sections.d.ts.map +1 -0
  139. package/dist/examples/form-dropdown-with-sections.js +13 -0
  140. package/dist/examples/form-dropdown-with-sections.js.map +1 -0
  141. package/dist/examples/form-dropdown-with-sections.vitest.d.ts +2 -0
  142. package/dist/examples/form-dropdown-with-sections.vitest.d.ts.map +1 -0
  143. package/dist/examples/form-dropdown-with-sections.vitest.js +75 -0
  144. package/dist/examples/form-dropdown-with-sections.vitest.js.map +1 -0
  145. package/dist/examples/form-dropdown.vitest.d.ts +2 -0
  146. package/dist/examples/form-dropdown.vitest.d.ts.map +1 -0
  147. package/dist/examples/form-dropdown.vitest.js +854 -0
  148. package/dist/examples/form-dropdown.vitest.js.map +1 -0
  149. package/dist/examples/form-multiselect-dropdown.d.ts +2 -0
  150. package/dist/examples/form-multiselect-dropdown.d.ts.map +1 -0
  151. package/dist/examples/form-multiselect-dropdown.js +13 -0
  152. package/dist/examples/form-multiselect-dropdown.js.map +1 -0
  153. package/dist/examples/form-scroll.d.ts.map +1 -1
  154. package/dist/examples/form-scroll.js +7 -1
  155. package/dist/examples/form-scroll.js.map +1 -1
  156. package/dist/examples/form-scroll.vitest.d.ts +2 -0
  157. package/dist/examples/form-scroll.vitest.d.ts.map +1 -0
  158. package/dist/examples/form-scroll.vitest.js +211 -0
  159. package/dist/examples/form-scroll.vitest.js.map +1 -0
  160. package/dist/examples/form-tagpicker.vitest.d.ts +2 -0
  161. package/dist/examples/form-tagpicker.vitest.d.ts.map +1 -0
  162. package/dist/examples/form-tagpicker.vitest.js +736 -0
  163. package/dist/examples/form-tagpicker.vitest.js.map +1 -0
  164. package/dist/examples/internal/descendants-filtering.js +1 -1
  165. package/dist/examples/internal/descendants-filtering.js.map +1 -1
  166. package/dist/examples/internal/descendants.js +1 -1
  167. package/dist/examples/internal/descendants.js.map +1 -1
  168. package/dist/examples/internal/nested-boxes.d.ts +2 -0
  169. package/dist/examples/internal/nested-boxes.d.ts.map +1 -0
  170. package/dist/examples/internal/nested-boxes.js +7 -0
  171. package/dist/examples/internal/nested-boxes.js.map +1 -0
  172. package/dist/examples/internal/rhf-custom-ref.js +2 -2
  173. package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
  174. package/dist/examples/internal/scrollbox-demo.js +3 -22
  175. package/dist/examples/internal/scrollbox-demo.js.map +1 -1
  176. package/dist/examples/internal/scrollbox-descendants.d.ts +2 -0
  177. package/dist/examples/internal/scrollbox-descendants.d.ts.map +1 -0
  178. package/dist/examples/internal/scrollbox-descendants.js +83 -0
  179. package/dist/examples/internal/scrollbox-descendants.js.map +1 -0
  180. package/dist/examples/internal/scrollbox-with-descendants.js +4 -8
  181. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
  182. package/dist/examples/internal/simple-scrollbox.vitest.d.ts +2 -0
  183. package/dist/examples/internal/simple-scrollbox.vitest.d.ts.map +1 -0
  184. package/dist/examples/internal/simple-scrollbox.vitest.js +96 -0
  185. package/dist/examples/internal/simple-scrollbox.vitest.js.map +1 -0
  186. package/dist/examples/internal/unicode-square-repro.d.ts +2 -0
  187. package/dist/examples/internal/unicode-square-repro.d.ts.map +1 -0
  188. package/dist/examples/internal/unicode-square-repro.js +7 -0
  189. package/dist/examples/internal/unicode-square-repro.js.map +1 -0
  190. package/dist/examples/list-detail-metadata.d.ts +2 -0
  191. package/dist/examples/list-detail-metadata.d.ts.map +1 -0
  192. package/dist/examples/list-detail-metadata.js +8 -0
  193. package/dist/examples/list-detail-metadata.js.map +1 -0
  194. package/dist/examples/list-dropdown-default.vitest.d.ts +2 -0
  195. package/dist/examples/list-dropdown-default.vitest.d.ts.map +1 -0
  196. package/dist/examples/list-dropdown-default.vitest.js +234 -0
  197. package/dist/examples/list-dropdown-default.vitest.js.map +1 -0
  198. package/dist/examples/list-fetch-data.vitest.d.ts +2 -0
  199. package/dist/examples/list-fetch-data.vitest.d.ts.map +1 -0
  200. package/dist/examples/list-fetch-data.vitest.js +111 -0
  201. package/dist/examples/list-fetch-data.vitest.js.map +1 -0
  202. package/dist/examples/list-filter-navigation.d.ts +2 -0
  203. package/dist/examples/list-filter-navigation.d.ts.map +1 -0
  204. package/dist/examples/list-filter-navigation.js +8 -0
  205. package/dist/examples/list-filter-navigation.js.map +1 -0
  206. package/dist/examples/list-scrollbox.vitest.d.ts +2 -0
  207. package/dist/examples/list-scrollbox.vitest.d.ts.map +1 -0
  208. package/dist/examples/list-scrollbox.vitest.js +93 -0
  209. package/dist/examples/list-scrollbox.vitest.js.map +1 -0
  210. package/dist/examples/list-with-detail-long.d.ts +2 -0
  211. package/dist/examples/list-with-detail-long.d.ts.map +1 -0
  212. package/dist/examples/list-with-detail-long.js +53 -0
  213. package/dist/examples/list-with-detail-long.js.map +1 -0
  214. package/dist/examples/list-with-detail.vitest.d.ts +2 -0
  215. package/dist/examples/list-with-detail.vitest.d.ts.map +1 -0
  216. package/dist/examples/list-with-detail.vitest.js +434 -0
  217. package/dist/examples/list-with-detail.vitest.js.map +1 -0
  218. package/dist/examples/list-with-dropdown.vitest.d.ts +2 -0
  219. package/dist/examples/list-with-dropdown.vitest.d.ts.map +1 -0
  220. package/dist/examples/list-with-dropdown.vitest.js +337 -0
  221. package/dist/examples/list-with-dropdown.vitest.js.map +1 -0
  222. package/dist/examples/list-with-sections.js +5 -1
  223. package/dist/examples/list-with-sections.js.map +1 -1
  224. package/dist/examples/list-with-sections.vitest.d.ts +2 -0
  225. package/dist/examples/list-with-sections.vitest.d.ts.map +1 -0
  226. package/dist/examples/list-with-sections.vitest.js +601 -0
  227. package/dist/examples/list-with-sections.vitest.js.map +1 -0
  228. package/dist/examples/scrollbox-vertical-centering.d.ts +6 -0
  229. package/dist/examples/scrollbox-vertical-centering.d.ts.map +1 -0
  230. package/dist/examples/scrollbox-vertical-centering.js +17 -0
  231. package/dist/examples/scrollbox-vertical-centering.js.map +1 -0
  232. package/dist/examples/simple-file-picker.vitest.d.ts +2 -0
  233. package/dist/examples/simple-file-picker.vitest.d.ts.map +1 -0
  234. package/dist/examples/simple-file-picker.vitest.js +678 -0
  235. package/dist/examples/simple-file-picker.vitest.js.map +1 -0
  236. package/dist/examples/simple-grid.vitest.d.ts +2 -0
  237. package/dist/examples/simple-grid.vitest.d.ts.map +1 -0
  238. package/dist/examples/simple-grid.vitest.js +521 -0
  239. package/dist/examples/simple-grid.vitest.js.map +1 -0
  240. package/dist/examples/simple-navigation.js +10 -4
  241. package/dist/examples/simple-navigation.js.map +1 -1
  242. package/dist/examples/simple-navigation.vitest.d.ts +2 -0
  243. package/dist/examples/simple-navigation.vitest.d.ts.map +1 -0
  244. package/dist/examples/simple-navigation.vitest.js +718 -0
  245. package/dist/examples/simple-navigation.vitest.js.map +1 -0
  246. package/dist/examples/store.vitest.d.ts +2 -0
  247. package/dist/examples/store.vitest.d.ts.map +1 -0
  248. package/dist/examples/store.vitest.js +69 -0
  249. package/dist/examples/store.vitest.js.map +1 -0
  250. package/dist/extensions/dev.d.ts +4 -2
  251. package/dist/extensions/dev.d.ts.map +1 -1
  252. package/dist/extensions/dev.js +61 -10
  253. package/dist/extensions/dev.js.map +1 -1
  254. package/dist/extensions/dev.vitest.d.ts +2 -0
  255. package/dist/extensions/dev.vitest.d.ts.map +1 -0
  256. package/dist/extensions/dev.vitest.js +197 -0
  257. package/dist/extensions/dev.vitest.js.map +1 -0
  258. package/dist/extensions/home.d.ts.map +1 -1
  259. package/dist/extensions/home.js +3 -0
  260. package/dist/extensions/home.js.map +1 -1
  261. package/dist/home-command.d.ts +8 -0
  262. package/dist/home-command.d.ts.map +1 -0
  263. package/dist/home-command.js +181 -0
  264. package/dist/home-command.js.map +1 -0
  265. package/dist/hover-repro.d.ts +2 -0
  266. package/dist/hover-repro.d.ts.map +1 -0
  267. package/dist/hover-repro.js +20 -0
  268. package/dist/hover-repro.js.map +1 -0
  269. package/dist/index.d.ts +1 -0
  270. package/dist/index.d.ts.map +1 -1
  271. package/dist/index.js +2 -0
  272. package/dist/index.js.map +1 -1
  273. package/dist/internal/dialog.d.ts +1 -0
  274. package/dist/internal/dialog.d.ts.map +1 -1
  275. package/dist/internal/dialog.js +27 -18
  276. package/dist/internal/dialog.js.map +1 -1
  277. package/dist/internal/navigation.d.ts +9 -1
  278. package/dist/internal/navigation.d.ts.map +1 -1
  279. package/dist/internal/navigation.js +5 -5
  280. package/dist/internal/navigation.js.map +1 -1
  281. package/dist/internal/offscreen.d.ts +6 -0
  282. package/dist/internal/offscreen.d.ts.map +1 -0
  283. package/dist/internal/offscreen.js +10 -0
  284. package/dist/internal/offscreen.js.map +1 -0
  285. package/dist/internal/providers.d.ts.map +1 -1
  286. package/dist/internal/providers.js +2 -2
  287. package/dist/internal/providers.js.map +1 -1
  288. package/dist/internal/scrollbox.d.ts +1 -10
  289. package/dist/internal/scrollbox.d.ts.map +1 -1
  290. package/dist/internal/scrollbox.js +2 -1
  291. package/dist/internal/scrollbox.js.map +1 -1
  292. package/dist/localstorage.d.ts +13 -0
  293. package/dist/localstorage.d.ts.map +1 -0
  294. package/dist/localstorage.js +190 -0
  295. package/dist/localstorage.js.map +1 -0
  296. package/dist/oauth.d.ts +142 -0
  297. package/dist/oauth.d.ts.map +1 -0
  298. package/dist/oauth.js +551 -0
  299. package/dist/oauth.js.map +1 -0
  300. package/dist/preferences.d.ts +23 -0
  301. package/dist/preferences.d.ts.map +1 -0
  302. package/dist/preferences.js +105 -0
  303. package/dist/preferences.js.map +1 -0
  304. package/dist/state.d.ts +2 -0
  305. package/dist/state.d.ts.map +1 -1
  306. package/dist/state.js +3 -0
  307. package/dist/state.js.map +1 -1
  308. package/dist/store.d.ts +21 -0
  309. package/dist/store.d.ts.map +1 -0
  310. package/dist/store.js +84 -0
  311. package/dist/store.js.map +1 -0
  312. package/dist/swift-loader.d.ts +3 -0
  313. package/dist/swift-loader.d.ts.map +1 -0
  314. package/dist/swift-loader.js +193 -0
  315. package/dist/swift-loader.js.map +1 -0
  316. package/dist/swift-runtime.d.ts +2 -0
  317. package/dist/swift-runtime.d.ts.map +1 -0
  318. package/dist/swift-runtime.js +27 -0
  319. package/dist/swift-runtime.js.map +1 -0
  320. package/dist/toast.d.ts +44 -0
  321. package/dist/toast.d.ts.map +1 -0
  322. package/dist/toast.js +221 -0
  323. package/dist/toast.js.map +1 -0
  324. package/dist/utils/file-system.d.ts +9 -0
  325. package/dist/utils/file-system.d.ts.map +1 -1
  326. package/dist/utils/file-system.js +49 -0
  327. package/dist/utils/file-system.js.map +1 -1
  328. package/dist/utils/run-command.d.ts +25 -1
  329. package/dist/utils/run-command.d.ts.map +1 -1
  330. package/dist/utils/run-command.js +47 -4
  331. package/dist/utils/run-command.js.map +1 -1
  332. package/dist/window.d.ts +12 -0
  333. package/dist/window.d.ts.map +1 -0
  334. package/dist/window.js +48 -0
  335. package/dist/window.js.map +1 -0
  336. package/package.json +12 -10
  337. package/src/apis/browser-extension.tsx +29 -0
  338. package/src/apis/localstorage.test.ts +14 -6
  339. package/src/apis/localstorage.tsx +8 -5
  340. package/src/apis/oauth.tsx +5 -1
  341. package/src/apis/preferences.tsx +48 -22
  342. package/src/build.test.tsx +52 -0
  343. package/src/build.tsx +2 -1
  344. package/src/cli.tsx +78 -37
  345. package/src/compile.tsx +22 -5
  346. package/src/components/actions.tsx +94 -32
  347. package/src/components/detail.tsx +4 -0
  348. package/src/components/dropdown.tsx +47 -14
  349. package/src/components/extension-preferences.tsx +44 -16
  350. package/src/components/form/checkbox.tsx +12 -6
  351. package/src/components/form/date-picker.tsx +12 -6
  352. package/src/components/form/description.tsx +7 -2
  353. package/src/components/form/dropdown.tsx +131 -119
  354. package/src/components/form/file-autocomplete.tsx +90 -108
  355. package/src/components/form/file-picker.tsx +54 -43
  356. package/src/components/form/form-end.tsx +23 -2
  357. package/src/components/form/index.tsx +152 -34
  358. package/src/components/form/password-field.tsx +12 -6
  359. package/src/components/form/text-area.tsx +13 -6
  360. package/src/components/form/text-field.tsx +13 -6
  361. package/src/components/form/types.tsx +6 -0
  362. package/src/components/form/with-left-border.tsx +41 -8
  363. package/src/components/icon.tsx +27 -8
  364. package/src/components/list.tsx +193 -74
  365. package/src/components/loading-bar.tsx +3 -2
  366. package/src/components/loading-text.tsx +79 -0
  367. package/src/descendants.tsx +1 -0
  368. package/src/examples/file-autocomplete.vitest.tsx +130 -125
  369. package/src/examples/form-basic.vitest.tsx +376 -176
  370. package/src/examples/form-dropdown.vitest.tsx +126 -126
  371. package/src/examples/form-scroll.tsx +2 -0
  372. package/src/examples/form-scroll.vitest.tsx +58 -58
  373. package/src/examples/form-tagpicker.vitest.tsx +99 -99
  374. package/src/examples/internal/descendants-filtering.tsx +1 -0
  375. package/src/examples/internal/descendants.tsx +1 -0
  376. package/src/examples/internal/rhf-custom-ref.tsx +2 -0
  377. package/src/examples/internal/scrollbox-demo.tsx +3 -27
  378. package/src/examples/internal/scrollbox-with-descendants.tsx +4 -7
  379. package/src/examples/internal/simple-scrollbox.vitest.tsx +7 -5
  380. package/src/examples/list-detail-metadata.tsx +49 -0
  381. package/src/examples/list-detail-metadata.vitest.tsx +88 -0
  382. package/src/examples/list-dropdown-default.vitest.tsx +51 -51
  383. package/src/examples/list-fetch-data.vitest.tsx +4 -4
  384. package/src/examples/list-scrollbox.vitest.tsx +73 -14
  385. package/src/examples/list-with-detail-long.tsx +70 -0
  386. package/src/examples/list-with-detail.vitest.tsx +198 -92
  387. package/src/examples/list-with-dropdown.vitest.tsx +53 -53
  388. package/src/examples/list-with-sections.tsx +1 -0
  389. package/src/examples/list-with-sections.vitest.tsx +213 -89
  390. package/src/examples/list-with-toast.vitest.tsx +4 -4
  391. package/src/examples/simple-file-picker.vitest.tsx +61 -471
  392. package/src/examples/simple-grid.vitest.tsx +238 -233
  393. package/src/examples/simple-navigation.tsx +15 -7
  394. package/src/examples/simple-navigation.vitest.tsx +121 -210
  395. package/src/examples/store.vitest.tsx +1 -1
  396. package/src/examples/swift-extension.vitest.tsx +148 -0
  397. package/src/examples/synonyms.vitest.tsx +159 -0
  398. package/src/extensions/dev.tsx +74 -7
  399. package/src/extensions/dev.vitest.tsx +97 -31
  400. package/src/extensions/home.tsx +6 -0
  401. package/src/index.tsx +3 -0
  402. package/src/internal/dialog.tsx +43 -30
  403. package/src/internal/navigation.tsx +3 -1
  404. package/src/internal/offscreen.tsx +15 -0
  405. package/src/internal/providers.tsx +4 -2
  406. package/src/internal/scrollbox.tsx +4 -8
  407. package/src/keyboard.test.tsx +69 -0
  408. package/src/state.tsx +7 -0
  409. package/src/swift-loader.tsx +239 -0
  410. package/src/swift-runtime.tsx +36 -0
  411. package/src/utils/file-system.ts +61 -0
  412. package/src/utils/run-command.tsx +75 -6
@@ -0,0 +1,148 @@
1
+ import { test, expect, beforeEach, afterEach } from 'vitest'
2
+ import { launchTerminal, Session } from 'tuistory/src'
3
+ import { execSync } from 'node:child_process'
4
+ import fs from 'node:fs'
5
+ import path from 'node:path'
6
+
7
+ const fixtureDir = path.resolve(__dirname, '../../fixtures/swift-extension')
8
+
9
+ // Find binary - check debug first, then release
10
+ function findSwiftBinary(): string {
11
+ const debugPath = path.join(fixtureDir, 'swift/.build/debug/SwiftAPI')
12
+ const releasePath = path.join(fixtureDir, 'swift/.build/release/SwiftAPI')
13
+ if (fs.existsSync(debugPath)) {
14
+ return debugPath
15
+ }
16
+ if (fs.existsSync(releasePath)) {
17
+ return releasePath
18
+ }
19
+ throw new Error(`Swift binary not found at ${debugPath} or ${releasePath}`)
20
+ }
21
+
22
+ const swiftBinary = findSwiftBinary()
23
+
24
+ test('swift binary returns items correctly', () => {
25
+ const stdout = execSync(`${swiftBinary} getItems`, { encoding: 'utf-8' })
26
+ expect(JSON.parse(stdout)).toMatchInlineSnapshot(`
27
+ [
28
+ {
29
+ "id": "1",
30
+ "subtitle": "Generated by Swift",
31
+ "title": "Swift Item One",
32
+ },
33
+ {
34
+ "id": "2",
35
+ "subtitle": "From native code",
36
+ "title": "Swift Item Two",
37
+ },
38
+ {
39
+ "id": "3",
40
+ "subtitle": "Fast and safe",
41
+ "title": "Swift Item Three",
42
+ },
43
+ ]
44
+ `)
45
+ })
46
+
47
+ test('swift binary greet function works', () => {
48
+ const stdout = execSync(`${swiftBinary} greet '"World"'`, { encoding: 'utf-8' })
49
+ expect(JSON.parse(stdout)).toMatchInlineSnapshot(`"Hello, World! Greetings from Swift."`)
50
+ })
51
+
52
+ let session: Session
53
+
54
+ beforeEach(async () => {
55
+ session = await launchTerminal({
56
+ command: 'bun',
57
+ args: ['src/cli.tsx', 'dev', fixtureDir],
58
+ cwd: path.resolve(__dirname, '../..'),
59
+ cols: 70,
60
+ rows: 25,
61
+ })
62
+ })
63
+
64
+ afterEach(() => {
65
+ session?.close()
66
+ })
67
+
68
+ test('swift extension dev mode shows command list', async () => {
69
+ // Wait for command list to appear
70
+ const commandList = await session.text({
71
+ waitFor: (text) => /Swift List/i.test(text),
72
+ timeout: 30000,
73
+ })
74
+
75
+ expect(commandList).toMatchInlineSnapshot(`
76
+ "
77
+
78
+
79
+ Swift Test Extension ───────────────────────────────────────────
80
+
81
+ Search commands...
82
+
83
+ Commands
84
+ β€ΊList Items Displays a simple list with some items view
85
+ Swift List Displays a list of items returned by a Swift f view
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+ ↡ run command ↑↓ navigate ^k actions"
100
+ `)
101
+ }, 60000)
102
+
103
+ test('swift extension runs Swift List command and shows items', async () => {
104
+ // Wait for command list to appear
105
+ await session.text({
106
+ waitFor: (text) => /Swift List/i.test(text),
107
+ timeout: 30000,
108
+ })
109
+
110
+ // Navigate down to Swift List (it's the second item)
111
+ await session.press('down')
112
+
113
+ // Select the Swift List command
114
+ await session.press('return')
115
+
116
+ // Wait for Swift items to load and display
117
+ const swiftListOutput = await session.text({
118
+ waitFor: (text) => text.includes('Swift Item One'),
119
+ timeout: 30000,
120
+ })
121
+
122
+ expect(swiftListOutput).toMatchInlineSnapshot(`
123
+ "
124
+
125
+
126
+ Swift Items ────────────────────────────────────────────────────
127
+
128
+ Search...
129
+
130
+ Items from Swift
131
+ β€ΊSwift Item One Generated by Swift
132
+ Swift Item Two From native code
133
+ Swift Item Three Fast and safe
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+ ↑↓ navigate ^k actions"
147
+ `)
148
+ }, 60000)
@@ -0,0 +1,159 @@
1
+ import { test, expect, beforeEach, afterEach, beforeAll } from 'vitest'
2
+ import { launchTerminal, Session } from 'tuistory/src'
3
+ import { execSync } from 'node:child_process'
4
+ import path from 'node:path'
5
+
6
+ const extensionDir = path.resolve(__dirname, '../../extensions/synonyms')
7
+
8
+ // Install dependencies before running tests
9
+ beforeAll(() => {
10
+ execSync('bun install', { cwd: extensionDir, stdio: 'inherit' })
11
+ }, 60000)
12
+
13
+ let session: Session
14
+
15
+ beforeEach(async () => {
16
+ session = await launchTerminal({
17
+ command: 'bun',
18
+ args: ['src/cli.tsx', 'dev', extensionDir],
19
+ cwd: path.resolve(__dirname, '../..'),
20
+ cols: 80,
21
+ rows: 30,
22
+ })
23
+ })
24
+
25
+ afterEach(() => {
26
+ session?.close()
27
+ })
28
+
29
+ test('synonyms extension shows preferences form on first launch', async () => {
30
+ // Wait for preferences form to appear (extension requires LLM provider setup)
31
+ await session.text({
32
+ waitFor: (text) => /LLM Provider/i.test(text),
33
+ timeout: 30000,
34
+ })
35
+
36
+ // Wait for auto-focus to settle
37
+ await session.waitIdle()
38
+
39
+ const preferencesForm = await session.text()
40
+
41
+ expect(preferencesForm).toMatchInlineSnapshot(`
42
+ "
43
+
44
+
45
+ β—‡ LLM Provider β–€
46
+ β”‚ Select...
47
+ β”‚
48
+ β”‚ β—‹ Raycast AI
49
+ β”‚ β—‹ OpenAI
50
+ β”‚ β—‹ OpenAI Compatible
51
+ β”‚ β—‹ Anthropic
52
+ β”‚ β—‹ Fireworks.ai
53
+ β”‚
54
+ β”‚ Select the LLM provider you want to use
55
+ β”‚
56
+ β—‡ OpenAI API Key
57
+ β”‚
58
+ β”‚ API Key for OpenAI
59
+ β”‚
60
+ β—‡ OpenAI Model
61
+ β”‚ gpt-4.1-mini
62
+ β”‚
63
+ β”‚ Model name for OpenAI
64
+ β”‚
65
+ β—‡ OpenAI Compatible URL
66
+ β”‚
67
+ β”‚
68
+ β”‚ Base URL for OpenAI compatible provider
69
+
70
+
71
+ ctrl ↡ submit tab navigate ^k actions"
72
+ `)
73
+ }, 60000)
74
+
75
+ test('synonyms extension preferences form can be navigated', async () => {
76
+ // Wait for preferences form to appear
77
+ await session.text({
78
+ waitFor: (text) => /LLM Provider/i.test(text),
79
+ timeout: 30000,
80
+ })
81
+
82
+ // Wait for auto-focus to settle
83
+ await session.waitIdle()
84
+
85
+ // Navigate down in the dropdown
86
+ await session.press('down')
87
+ await session.press('down')
88
+
89
+ const afterNavigate = await session.text()
90
+ expect(afterNavigate).toMatchInlineSnapshot(`
91
+ "
92
+
93
+
94
+ β—‡ LLM Provider β–€
95
+ β”‚ Select...
96
+ β”‚
97
+ β”‚ β—‹ Raycast AI
98
+ β”‚ β—‹ OpenAI
99
+ β”‚ β—‹ OpenAI Compatible
100
+ β”‚ β—‹ Anthropic
101
+ β”‚ β—‹ Fireworks.ai
102
+ β”‚
103
+ β”‚ Select the LLM provider you want to use
104
+ β”‚
105
+ β—‡ OpenAI API Key
106
+ β”‚
107
+ β”‚ API Key for OpenAI
108
+ β”‚
109
+ β—‡ OpenAI Model
110
+ β”‚ gpt-4.1-mini
111
+ β”‚
112
+ β”‚ Model name for OpenAI
113
+ β”‚
114
+ β—‡ OpenAI Compatible URL
115
+ β”‚
116
+ β”‚
117
+ β”‚ Base URL for OpenAI compatible provider
118
+
119
+
120
+ ctrl ↡ submit tab navigate ^k actions"
121
+ `)
122
+
123
+ // Press tab to move to next field
124
+ await session.press('tab')
125
+
126
+ const afterTab = await session.text()
127
+ expect(afterTab).toMatchInlineSnapshot(`
128
+ "
129
+
130
+
131
+ β—† LLM Provider β–€
132
+ β”‚ Select...
133
+ β”‚
134
+ β”‚β€Ί β—‹ Raycast AI
135
+ β”‚ β—‹ OpenAI
136
+ β”‚ β—‹ OpenAI Compatible
137
+ β”‚ β—‹ Anthropic
138
+ β”‚ β—‹ Fireworks.ai
139
+ β”‚
140
+ β”‚ Select the LLM provider you want to use
141
+ β”‚
142
+ β—‡ OpenAI API Key
143
+ β”‚
144
+ β”‚ API Key for OpenAI
145
+ β”‚
146
+ β—‡ OpenAI Model
147
+ β”‚ gpt-4.1-mini
148
+ β”‚
149
+ β”‚ Model name for OpenAI
150
+ β”‚
151
+ β—‡ OpenAI Compatible URL
152
+ β”‚
153
+ β”‚
154
+ β”‚ Base URL for OpenAI compatible provider
155
+
156
+
157
+ ctrl ↡ submit tab navigate ^k actions"
158
+ `)
159
+ }, 60000)
@@ -11,7 +11,12 @@ import { showToast, Toast } from 'termcast/src/apis/toast'
11
11
  import { Icon } from 'termcast'
12
12
  import { getCommandsWithFiles, CommandWithFile } from '../package-json'
13
13
  import { buildExtensionCommands } from '../build'
14
- import { runCommand, clearCommandArguments } from '../utils/run-command'
14
+ import {
15
+ runCommand,
16
+ clearCommandArguments,
17
+ parseExtensionArgs,
18
+ handleHelpFlag,
19
+ } from '../utils/run-command'
15
20
 
16
21
  interface BundledCommand extends CommandWithFile {
17
22
  bundledPath: string
@@ -21,15 +26,18 @@ interface BundledCommand extends CommandWithFile {
21
26
  function ExtensionCommandsList({
22
27
  extensionPath,
23
28
  commands,
29
+ skipArgv,
24
30
  }: {
25
31
  extensionPath: string
26
32
  commands: BundledCommand[]
33
+ skipArgv?: number
27
34
  }): any {
28
35
  const { push } = useNavigation()
29
36
  const { packageJson } = getCommandsWithFiles({
30
37
  packageJsonPath: path.join(extensionPath, 'package.json'),
31
38
  })
32
- const devRebuildCount = useStore((state) => state.devRebuildCount)
39
+
40
+ const visibleCommands = commands.filter((cmd) => cmd.mode !== 'menu-bar')
33
41
 
34
42
  const handleCommandSelect = async (command: BundledCommand) => {
35
43
  clearCommandArguments()
@@ -51,7 +59,6 @@ function ExtensionCommandsList({
51
59
  bundledPath: command.bundledPath,
52
60
  Component: command.Component,
53
61
  push,
54
- cacheBustParam: String(devRebuildCount),
55
62
  })
56
63
  } catch (error: any) {
57
64
  await showToast({
@@ -62,13 +69,43 @@ function ExtensionCommandsList({
62
69
  }
63
70
  }
64
71
 
72
+ // Auto-run command from CLI arg or single command
73
+ React.useLayoutEffect(() => {
74
+ // Only parse argv on initial load (when skipArgv is provided), not on rebuilds
75
+ if (skipArgv != null) {
76
+ const { commandName } = parseExtensionArgs({ skipArgv })
77
+
78
+ if (commandName) {
79
+ const command = visibleCommands.find((cmd) => cmd.name === commandName)
80
+ if (command) {
81
+ handleCommandSelect(command)
82
+ } else {
83
+ showToast({
84
+ style: Toast.Style.Failure,
85
+ title: 'Command not found',
86
+ message: `No command named "${commandName}"`,
87
+ })
88
+ }
89
+ return
90
+ }
91
+ }
92
+
93
+ if (visibleCommands.length === 1) {
94
+ handleCommandSelect(visibleCommands[0])
95
+ }
96
+ }, [])
97
+
98
+ if (visibleCommands.length === 1) {
99
+ return null
100
+ }
101
+
65
102
  return (
66
103
  <List
67
104
  navigationTitle={packageJson.title || 'Extension Commands'}
68
105
  searchBarPlaceholder='Search commands...'
69
106
  >
70
107
  <List.Section title='Commands'>
71
- {commands.filter((cmd) => cmd.mode !== 'menu-bar').map((command) => (
108
+ {visibleCommands.map((command) => (
72
109
  <List.Item
73
110
  key={command.name}
74
111
  id={command.name}
@@ -111,7 +148,7 @@ function ExtensionCommandsList({
111
148
  ))}
112
149
  </List.Section>
113
150
 
114
- {commands.length === 0 && (
151
+ {visibleCommands.length === 0 && (
115
152
  <List.Section title='No Commands'>
116
153
  <List.Item
117
154
  title='No commands found'
@@ -125,8 +162,10 @@ function ExtensionCommandsList({
125
162
 
126
163
  export async function startDevMode({
127
164
  extensionPath,
165
+ skipArgv = 0,
128
166
  }: {
129
167
  extensionPath: string
168
+ skipArgv?: number
130
169
  }): Promise<void> {
131
170
  const resolvedPath = path.resolve(extensionPath)
132
171
 
@@ -149,13 +188,24 @@ export async function startDevMode({
149
188
  target: 'bun',
150
189
  })
151
190
 
191
+ // Handle --help before rendering
192
+ handleHelpFlag({
193
+ extensionName: packageJson.title || packageJson.name,
194
+ commands: commands.map((cmd) => ({
195
+ name: cmd.name,
196
+ title: cmd.title,
197
+ description: cmd.description,
198
+ })),
199
+ skipArgv,
200
+ })
201
+
152
202
  // Reset state and set extension information
153
203
  useStore.setState({
154
204
  ...useStore.getInitialState(),
155
205
  extensionPath: resolvedPath,
156
206
  extensionPackageJson: packageJson,
157
207
  devElement: (
158
- <ExtensionCommandsList extensionPath={resolvedPath} commands={commands} />
208
+ <ExtensionCommandsList extensionPath={resolvedPath} commands={commands} skipArgv={skipArgv} />
159
209
  ),
160
210
  devRebuildCount: 1,
161
211
  })
@@ -174,6 +224,7 @@ export async function startDevMode({
174
224
  export async function startCompiledExtension({
175
225
  extensionPath,
176
226
  compiledCommands,
227
+ skipArgv = 0,
177
228
  }: {
178
229
  extensionPath: string
179
230
  compiledCommands: Array<{
@@ -181,6 +232,7 @@ export async function startCompiledExtension({
181
232
  bundledPath: string
182
233
  Component: (props: any) => any
183
234
  }>
235
+ skipArgv?: number
184
236
  }): Promise<void> {
185
237
  const packageJsonPath = path.join(extensionPath, 'package.json')
186
238
  const { packageJson, commands: commandsMetadata } = getCommandsWithFiles({
@@ -196,13 +248,25 @@ export async function startCompiledExtension({
196
248
  }
197
249
  })
198
250
 
251
+ // Handle --help before rendering
252
+ handleHelpFlag({
253
+ extensionName: packageJson.title || packageJson.name,
254
+ commands: commands.map((cmd) => ({
255
+ name: cmd.name,
256
+ title: cmd.title,
257
+ description: cmd.description,
258
+ })),
259
+ skipArgv,
260
+ })
261
+
199
262
  useStore.setState({
200
263
  ...useStore.getInitialState(),
201
264
  extensionPath,
202
265
  extensionPackageJson: packageJson,
203
266
  devElement: (
204
- <ExtensionCommandsList extensionPath={extensionPath} commands={commands} />
267
+ <ExtensionCommandsList extensionPath={extensionPath} commands={commands} skipArgv={skipArgv} />
205
268
  ),
269
+ devRebuildCount: 1,
206
270
  })
207
271
 
208
272
  function App(): any {
@@ -242,6 +306,9 @@ export async function triggerRebuild({
242
306
  />
243
307
  ),
244
308
  devRebuildCount: state.devRebuildCount + 1,
309
+ navigationStack: [], // Reset navigation so NavigationProvider re-initializes with new devElement
310
+ dialogStack: [], // Clear any open dialogs/toasts on rebuild
311
+ toast: null,
245
312
  })
246
313
  } catch (error: any) {
247
314
  await showToast({
@@ -38,11 +38,10 @@ test('dev command shows extension commands list', async () => {
38
38
  β€ΊList Items Displays a simple list with some ite view β–€
39
39
  Search Items Search and filter through a list o view
40
40
  Google Oauth view
41
- usePromise Demo Shows how to use the usePromise view
42
- β–Ό
41
+ usePromise Demo Shows how to use the usePromise view β–Ό
43
42
 
44
43
 
45
- ↡ select ↑↓ navigate ^k actions"
44
+ ↡ run command ↑↓ navigate ^k actions"
46
45
  `)
47
46
  }, 30000)
48
47
 
@@ -68,15 +67,14 @@ test('selecting command with arguments shows arguments form', async () => {
68
67
 
69
68
  Search commands...
70
69
 
71
- Search Items Search and filter through a list o view β–²
72
- Google Oauth view
73
- usePromise Demo Shows how to use the usePromise view β–„
70
+ usePromise Demo Shows how to use the usePromise view β–²
74
71
  Show State Shows the current application state view
75
72
  β€ΊWith Arguments Demonstrates command arguments ( view
73
+ Quick Action Copies current timestamp to cli no-view β–„
76
74
  β–Ό
77
75
 
78
76
 
79
- ↡ select ↑↓ navigate ^k actions"
77
+ ↡ run command ↑↓ navigate ^k actions"
80
78
  `)
81
79
 
82
80
  // Select the command to show arguments form (enter opens action panel, enter again runs)
@@ -93,19 +91,19 @@ test('selecting command with arguments shows arguments form', async () => {
93
91
  "
94
92
 
95
93
 
96
- β–ͺ With Arguments β–ˆ
97
- β”‚ Enter the arguments to run this command. β–€
94
+ β–  With Arguments β–ˆ
95
+ β”‚ Enter the arguments to run this command.
96
+ β”‚
97
+ β—‡ Search query
98
+ β”‚ Search query
98
99
  β”‚
99
- β—† Search query
100
- ┃ Search query
101
- ┃
102
100
  β—‡ Secret key
103
101
  β”‚ Secret key
104
102
  β—‡ Category
105
103
  β”‚ Category
106
104
 
107
105
 
108
- alt ↡ submit ↑↓ navigate ^k actions"
106
+ ctrl ↡ submit tab navigate ^k actions"
109
107
  `)
110
108
  }, 30000)
111
109
 
@@ -137,19 +135,19 @@ test('can fill arguments and run command', async () => {
137
135
  "
138
136
 
139
137
 
140
- β–ͺ With Arguments β–ˆ
141
- β”‚ Enter the arguments to run this command. β–€
138
+ β–  With Arguments β–ˆ
139
+ β”‚ Enter the arguments to run this command.
140
+ β”‚
141
+ β—‡ Search query
142
+ β”‚ Search query
142
143
  β”‚
143
- β—† Search query
144
- ┃ my search term
145
- ┃
146
144
  β—‡ Secret key
147
145
  β”‚ Secret key
148
146
  β—‡ Category
149
147
  β”‚ Category
150
148
 
151
149
 
152
- alt ↡ submit ↑↓ navigate ^k actions"
150
+ ctrl ↡ submit tab navigate ^k actions"
153
151
  `)
154
152
 
155
153
  // Submit the form with Alt+Enter (opens action panel), then Enter (selects submit)
@@ -171,15 +169,15 @@ test('can fill arguments and run command', async () => {
171
169
  Search...
172
170
 
173
171
  Received Arguments
174
- β€ΊSearch Query my search term
175
- Secret Key (empty)
176
- Category (empty)
172
+ β€Ίβ–Ό Search Query (empty)
173
+ β–Ό Secret Key (empty)
174
+ β–Ό Category (empty)
177
175
 
178
176
 
179
177
 
180
- ↡ seleβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
181
- β”‚ βœ“ Copied to Clipboard - my search term β”‚
182
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
178
+ ↡ copy valβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
179
+ β”‚ βœ“ Copied to Clipboard - (empty) β”‚
180
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
183
181
  `)
184
182
  }, 30000)
185
183
 
@@ -208,13 +206,81 @@ test('can run simple view command without arguments', async () => {
208
206
  Search...
209
207
 
210
208
  Items β–²
211
- β€ΊFirst Item This is the first item β–ˆ
212
- Second Item This is the second item
213
- Third Item This is the third item
214
- Fourth Item This is the fourth item
215
- β–Ό
209
+ β€Ίβ–² First Item This is the first item β–ˆ
210
+ β–² Second Item This is the second item
211
+ β–² Third Item This is the third item
212
+ β–² Fourth Item This is the fourth item β–Ό
216
213
 
217
214
 
218
- ↡ select ↑↓ navigate ^k actions"
215
+ ↡ copy item title ↑↓ navigate ^k actions"
219
216
  `)
220
217
  }, 30000)
218
+
219
+ test('hot reload updates TUI when source file changes', async () => {
220
+ const hotReloadFixtureDir = path.resolve(__dirname, '../../fixtures/hot-reload-extension')
221
+ const sourceFilePath = path.join(hotReloadFixtureDir, 'src/detail-view.tsx')
222
+ const fs = await import('node:fs')
223
+
224
+ // Read original content to restore later
225
+ const originalContent = fs.readFileSync(sourceFilePath, 'utf-8')
226
+
227
+ // Start a new session for this test
228
+ const hotReloadSession = await launchTerminal({
229
+ command: 'bun',
230
+ args: ['src/cli.tsx', 'dev', hotReloadFixtureDir],
231
+ cols: 60,
232
+ rows: 16,
233
+ })
234
+
235
+ try {
236
+ // Wait for the extension to load
237
+ await hotReloadSession.text({
238
+ waitFor: (text) => /Hot Reload Test/i.test(text) && /Detail View/i.test(text),
239
+ timeout: 5000,
240
+ })
241
+ await hotReloadSession.waitIdle()
242
+
243
+ // Run the Detail View command
244
+ await hotReloadSession.press('enter')
245
+ await hotReloadSession.press('enter')
246
+ await hotReloadSession.waitIdle()
247
+
248
+ // Wait for the detail view to show
249
+ await hotReloadSession.text({
250
+ waitFor: (text) => /MARKER_VALUE/i.test(text),
251
+ timeout: 10000,
252
+ })
253
+
254
+ // Generate a random number
255
+ const randomNumber = Math.floor(Math.random() * 1000000)
256
+
257
+ // Update the source file with the random number
258
+ const newContent = originalContent.replace('MARKER_VALUE', `UPDATED_${randomNumber}`)
259
+ fs.writeFileSync(sourceFilePath, newContent)
260
+
261
+ // Wait for rebuild - navigation resets to commands list
262
+ await hotReloadSession.text({
263
+ waitFor: (text) => /Hot Reload Test/i.test(text) && /Detail View/i.test(text),
264
+ timeout: 5000,
265
+ })
266
+ await hotReloadSession.waitIdle()
267
+
268
+ // Run the command again to see updated content
269
+ await hotReloadSession.press('enter')
270
+ await hotReloadSession.press('enter')
271
+ await hotReloadSession.waitIdle()
272
+
273
+ // Wait for the updated content
274
+ await hotReloadSession.text({
275
+ waitFor: (text) => text.includes(`UPDATED_${randomNumber}`),
276
+ timeout: 10000,
277
+ })
278
+
279
+ const updatedSnapshot = await hotReloadSession.text()
280
+ expect(updatedSnapshot).toContain(`UPDATED_${randomNumber}`)
281
+ } finally {
282
+ // Restore original content
283
+ fs.writeFileSync(sourceFilePath, originalContent)
284
+ hotReloadSession?.close()
285
+ }
286
+ }, 60000)