buninu-linux 0.2.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 (621) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES/Bun-LICENSE.md +84 -0
  3. package/LICENSES/GCC-Runtime-Library-Exception-3.1.txt +73 -0
  4. package/LICENSES/GPL-2.0.txt +338 -0
  5. package/LICENSES/GPL-3.0.txt +674 -0
  6. package/LICENSES/LGPL-2.1.txt +501 -0
  7. package/LICENSES/Linux-syscall-note.txt +25 -0
  8. package/LICENSES/musl-COPYRIGHT.txt +193 -0
  9. package/NOTICE.md +184 -0
  10. package/README.md +537 -0
  11. package/build-image.sh +44 -0
  12. package/build-uki.sh +75 -0
  13. package/fetch-alpine.sh +80 -0
  14. package/fetch-bun.sh +41 -0
  15. package/fetch-plus-build.sh +11 -0
  16. package/hello/build-hello.sh +14 -0
  17. package/hello/hello.c +50 -0
  18. package/index.js +235 -0
  19. package/initramfs/bin/sh +3 -0
  20. package/initramfs/buninu/.bashrc +11 -0
  21. package/initramfs/buninu/ARCHITECTURE.md +452 -0
  22. package/initramfs/buninu/CHANGELOG.md +580 -0
  23. package/initramfs/buninu/LICENSE +21 -0
  24. package/initramfs/buninu/README.md +1161 -0
  25. package/initramfs/buninu/apps/bun/make-bunBin.sh +99 -0
  26. package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +493 -0
  27. package/initramfs/buninu/apps/bunmsh/LICENSE +21 -0
  28. package/initramfs/buninu/apps/bunmsh/LICENSE-MICRO +58 -0
  29. package/initramfs/buninu/apps/bunmsh/LICENSE-MKSH +295 -0
  30. package/initramfs/buninu/apps/bunmsh/PORTING.md +192 -0
  31. package/initramfs/buninu/apps/bunmsh/README.md +1042 -0
  32. package/initramfs/buninu/apps/bunmsh/bunmsh +7 -0
  33. package/initramfs/buninu/apps/bunmsh/bunshell.md +61 -0
  34. package/initramfs/buninu/apps/bunmsh/help/README.md +2237 -0
  35. package/initramfs/buninu/apps/bunmsh/help/alias.md +22 -0
  36. package/initramfs/buninu/apps/bunmsh/help/basename.md +38 -0
  37. package/initramfs/buninu/apps/bunmsh/help/builtin.md +25 -0
  38. package/initramfs/buninu/apps/bunmsh/help/bun.md +27 -0
  39. package/initramfs/buninu/apps/bunmsh/help/bunmsh.md +47 -0
  40. package/initramfs/buninu/apps/bunmsh/help/cat.md +31 -0
  41. package/initramfs/buninu/apps/bunmsh/help/catfancy.md +35 -0
  42. package/initramfs/buninu/apps/bunmsh/help/cd.md +22 -0
  43. package/initramfs/buninu/apps/bunmsh/help/chmod.md +27 -0
  44. package/initramfs/buninu/apps/bunmsh/help/clear.md +21 -0
  45. package/initramfs/buninu/apps/bunmsh/help/colon.md +22 -0
  46. package/initramfs/buninu/apps/bunmsh/help/command.md +27 -0
  47. package/initramfs/buninu/apps/bunmsh/help/cp.md +28 -0
  48. package/initramfs/buninu/apps/bunmsh/help/curl.md +181 -0
  49. package/initramfs/buninu/apps/bunmsh/help/cut.md +25 -0
  50. package/initramfs/buninu/apps/bunmsh/help/date.md +25 -0
  51. package/initramfs/buninu/apps/bunmsh/help/dirname.md +25 -0
  52. package/initramfs/buninu/apps/bunmsh/help/echo.md +25 -0
  53. package/initramfs/buninu/apps/bunmsh/help/env.md +27 -0
  54. package/initramfs/buninu/apps/bunmsh/help/eval.md +22 -0
  55. package/initramfs/buninu/apps/bunmsh/help/exec.md +22 -0
  56. package/initramfs/buninu/apps/bunmsh/help/exit.md +21 -0
  57. package/initramfs/buninu/apps/bunmsh/help/export.md +22 -0
  58. package/initramfs/buninu/apps/bunmsh/help/false.md +22 -0
  59. package/initramfs/buninu/apps/bunmsh/help/find.md +33 -0
  60. package/initramfs/buninu/apps/bunmsh/help/getopts.md +28 -0
  61. package/initramfs/buninu/apps/bunmsh/help/grep.md +34 -0
  62. package/initramfs/buninu/apps/bunmsh/help/head.md +27 -0
  63. package/initramfs/buninu/apps/bunmsh/help/kill.md +43 -0
  64. package/initramfs/buninu/apps/bunmsh/help/ln.md +28 -0
  65. package/initramfs/buninu/apps/bunmsh/help/ls.md +37 -0
  66. package/initramfs/buninu/apps/bunmsh/help/lsbun.md +29 -0
  67. package/initramfs/buninu/apps/bunmsh/help/lsfancy.md +37 -0
  68. package/initramfs/buninu/apps/bunmsh/help/md5sum.md +21 -0
  69. package/initramfs/buninu/apps/bunmsh/help/mkdir.md +27 -0
  70. package/initramfs/buninu/apps/bunmsh/help/mktemp.md +26 -0
  71. package/initramfs/buninu/apps/bunmsh/help/mv.md +26 -0
  72. package/initramfs/buninu/apps/bunmsh/help/print.md +30 -0
  73. package/initramfs/buninu/apps/bunmsh/help/printf.md +25 -0
  74. package/initramfs/buninu/apps/bunmsh/help/pspa.md +38 -0
  75. package/initramfs/buninu/apps/bunmsh/help/pspac.md +57 -0
  76. package/initramfs/buninu/apps/bunmsh/help/pwd.md +21 -0
  77. package/initramfs/buninu/apps/bunmsh/help/read.md +27 -0
  78. package/initramfs/buninu/apps/bunmsh/help/readonly.md +27 -0
  79. package/initramfs/buninu/apps/bunmsh/help/realpath.md +21 -0
  80. package/initramfs/buninu/apps/bunmsh/help/rm.md +33 -0
  81. package/initramfs/buninu/apps/bunmsh/help/rmdir.md +27 -0
  82. package/initramfs/buninu/apps/bunmsh/help/sed.md +29 -0
  83. package/initramfs/buninu/apps/bunmsh/help/seq.md +29 -0
  84. package/initramfs/buninu/apps/bunmsh/help/serve.md +50 -0
  85. package/initramfs/buninu/apps/bunmsh/help/set.md +26 -0
  86. package/initramfs/buninu/apps/bunmsh/help/sha256sum.md +21 -0
  87. package/initramfs/buninu/apps/bunmsh/help/shift.md +23 -0
  88. package/initramfs/buninu/apps/bunmsh/help/sleep.md +25 -0
  89. package/initramfs/buninu/apps/bunmsh/help/sort.md +29 -0
  90. package/initramfs/buninu/apps/bunmsh/help/source.md +23 -0
  91. package/initramfs/buninu/apps/bunmsh/help/tab.md +30 -0
  92. package/initramfs/buninu/apps/bunmsh/help/tac.md +29 -0
  93. package/initramfs/buninu/apps/bunmsh/help/tail.md +27 -0
  94. package/initramfs/buninu/apps/bunmsh/help/tee.md +25 -0
  95. package/initramfs/buninu/apps/bunmsh/help/test.md +30 -0
  96. package/initramfs/buninu/apps/bunmsh/help/time.md +29 -0
  97. package/initramfs/buninu/apps/bunmsh/help/touch.md +26 -0
  98. package/initramfs/buninu/apps/bunmsh/help/tr.md +26 -0
  99. package/initramfs/buninu/apps/bunmsh/help/true.md +22 -0
  100. package/initramfs/buninu/apps/bunmsh/help/type.md +21 -0
  101. package/initramfs/buninu/apps/bunmsh/help/umask.md +22 -0
  102. package/initramfs/buninu/apps/bunmsh/help/unalias.md +28 -0
  103. package/initramfs/buninu/apps/bunmsh/help/uname.md +31 -0
  104. package/initramfs/buninu/apps/bunmsh/help/unset.md +28 -0
  105. package/initramfs/buninu/apps/bunmsh/help/wc.md +27 -0
  106. package/initramfs/buninu/apps/bunmsh/help/whence.md +26 -0
  107. package/initramfs/buninu/apps/bunmsh/help/which.md +41 -0
  108. package/initramfs/buninu/apps/bunmsh/help/yes.md +23 -0
  109. package/initramfs/buninu/apps/bunmsh/index.js +7 -0
  110. package/initramfs/buninu/apps/bunmsh/package.json +30 -0
  111. package/initramfs/buninu/apps/bunmsh/serve.js +468 -0
  112. package/initramfs/buninu/apps/bunmsh/single-exe/README.md +402 -0
  113. package/initramfs/buninu/apps/bunmsh/single-exe/assetsHelper.js +371 -0
  114. package/initramfs/buninu/apps/bunmsh/single-exe/assetsLoader.mjs +85 -0
  115. package/initramfs/buninu/apps/bunmsh/single-exe/assetsPacker.js +218 -0
  116. package/initramfs/buninu/apps/bunmsh/single-exe/compiled.js +527 -0
  117. package/initramfs/buninu/apps/bunmsh/single-exe/entry.mjs +17 -0
  118. package/initramfs/buninu/apps/bunmsh/src/cat-fancy.js +117 -0
  119. package/initramfs/buninu/apps/bunmsh/src/cat-operands.js +31 -0
  120. package/initramfs/buninu/apps/bunmsh/src/completion.js +363 -0
  121. package/initramfs/buninu/apps/bunmsh/src/curl.js +1191 -0
  122. package/initramfs/buninu/apps/bunmsh/src/environment.js +61 -0
  123. package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +186 -0
  124. package/initramfs/buninu/apps/bunmsh/src/find.js +119 -0
  125. package/initramfs/buninu/apps/bunmsh/src/history.js +198 -0
  126. package/initramfs/buninu/apps/bunmsh/src/main.js +850 -0
  127. package/initramfs/buninu/apps/bunmsh/src/mouse.js +103 -0
  128. package/initramfs/buninu/apps/bunmsh/src/shell.js +3932 -0
  129. package/initramfs/buninu/apps/bunmsh/test/catfancy.test.js +102 -0
  130. package/initramfs/buninu/apps/bunmsh/test/curl.test.js +335 -0
  131. package/initramfs/buninu/apps/bunmsh/test/reference-source.sh +1 -0
  132. package/initramfs/buninu/apps/bunmsh/test/reference.test.js +817 -0
  133. package/initramfs/buninu/apps/bunmsh/test/serve/README.md +3 -0
  134. package/initramfs/buninu/apps/bunmsh/test/serve/bad.json +1 -0
  135. package/initramfs/buninu/apps/bunmsh/test/serve/data.json +1 -0
  136. package/initramfs/buninu/apps/bunmsh/test/serve/data.json5 +1 -0
  137. package/initramfs/buninu/apps/bunmsh/test/serve/data.jsonc +2 -0
  138. package/initramfs/buninu/apps/bunmsh/test/serve/data.jsonl +2 -0
  139. package/initramfs/buninu/apps/bunmsh/test/serve/data.ndjson +1 -0
  140. package/initramfs/buninu/apps/bunmsh/test/serve/data.toml +3 -0
  141. package/initramfs/buninu/apps/bunmsh/test/serve/data.ts +15 -0
  142. package/initramfs/buninu/apps/bunmsh/test/serve/data.xml +1 -0
  143. package/initramfs/buninu/apps/bunmsh/test/serve/data.yaml +3 -0
  144. package/initramfs/buninu/apps/bunmsh/test/serve/data.yml +1 -0
  145. package/initramfs/buninu/apps/bunmsh/test/serve.test.js +241 -0
  146. package/initramfs/buninu/apps/bunmsh/test/shell.test.js +2092 -0
  147. package/initramfs/buninu/apps/bunmsh/test/shell1.test.js +1087 -0
  148. package/initramfs/buninu/apps/bunmsh/test/shell2.test.js +457 -0
  149. package/initramfs/buninu/apps/bunmsh/test/shell3.test.js +741 -0
  150. package/initramfs/buninu/apps/bunmsh/test/t.sh +12 -0
  151. package/initramfs/buninu/apps/bunx/bunx.js +172 -0
  152. package/initramfs/buninu/apps/cmdlist +48 -0
  153. package/initramfs/buninu/apps/jsgotty/LICENSE +23 -0
  154. package/initramfs/buninu/apps/jsgotty/LICENSES/Apache-2.0-zmodem.js.txt +201 -0
  155. package/initramfs/buninu/apps/jsgotty/NOTICE +21 -0
  156. package/initramfs/buninu/apps/jsgotty/README.en.md +294 -0
  157. package/initramfs/buninu/apps/jsgotty/README.md +296 -0
  158. package/initramfs/buninu/apps/jsgotty/client.mjs +700 -0
  159. package/initramfs/buninu/apps/jsgotty/env.sh +2 -0
  160. package/initramfs/buninu/apps/jsgotty/gotty.js +3334 -0
  161. package/initramfs/buninu/apps/jsgotty/node_modules/.package-lock.json +35 -0
  162. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/LICENSE.md +9 -0
  163. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/README.md +319 -0
  164. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/common.gypi +20 -0
  165. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/except.gypi +25 -0
  166. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/index.js +12 -0
  167. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  168. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi-inl.h +6607 -0
  169. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi.h +3201 -0
  170. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/node_addon_api.gyp +32 -0
  171. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/node_api.gyp +9 -0
  172. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/noexcept.gypi +26 -0
  173. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/nothing.c +0 -0
  174. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/package-support.json +21 -0
  175. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/package.json +480 -0
  176. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/README.md +73 -0
  177. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/check-napi.js +99 -0
  178. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/clang-format.js +71 -0
  179. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/conversion.js +301 -0
  180. package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/eslint-format.js +79 -0
  181. package/initramfs/buninu/apps/jsgotty/node_modules/ws/LICENSE +20 -0
  182. package/initramfs/buninu/apps/jsgotty/node_modules/ws/README.md +548 -0
  183. package/initramfs/buninu/apps/jsgotty/node_modules/ws/browser.js +8 -0
  184. package/initramfs/buninu/apps/jsgotty/node_modules/ws/index.js +22 -0
  185. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/buffer-util.js +131 -0
  186. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/constants.js +19 -0
  187. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/event-target.js +292 -0
  188. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/extension.js +203 -0
  189. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/limiter.js +55 -0
  190. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/permessage-deflate.js +528 -0
  191. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/receiver.js +760 -0
  192. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/sender.js +607 -0
  193. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/stream.js +161 -0
  194. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/subprotocol.js +62 -0
  195. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/validation.js +152 -0
  196. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/websocket-server.js +562 -0
  197. package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/websocket.js +1407 -0
  198. package/initramfs/buninu/apps/jsgotty/node_modules/ws/package.json +70 -0
  199. package/initramfs/buninu/apps/jsgotty/node_modules/ws/wrapper.mjs +21 -0
  200. package/initramfs/buninu/apps/jsgotty/package-lock.json +46 -0
  201. package/initramfs/buninu/apps/jsgotty/package.json +19 -0
  202. package/initramfs/buninu/apps/jsgotty/patches/windowsPtyAgent.js +326 -0
  203. package/initramfs/buninu/apps/jsgotty/patches/windowsTerminal.js +204 -0
  204. package/initramfs/buninu/apps/jsgotty/rz.js +199 -0
  205. package/initramfs/buninu/apps/jsgotty/single-exe/README.md +384 -0
  206. package/initramfs/buninu/apps/jsgotty/single-exe/assetsHelper.js +371 -0
  207. package/initramfs/buninu/apps/jsgotty/single-exe/assetsLoader.mjs +85 -0
  208. package/initramfs/buninu/apps/jsgotty/single-exe/assetsPacker.js +218 -0
  209. package/initramfs/buninu/apps/jsgotty/single-exe/compiled.js +498 -0
  210. package/initramfs/buninu/apps/jsgotty/single-exe/entry.mjs +23 -0
  211. package/initramfs/buninu/apps/jsgotty/single-exe/packAssets.sh +7 -0
  212. package/initramfs/buninu/apps/jsgotty/static/css/index.css +13 -0
  213. package/initramfs/buninu/apps/jsgotty/static/css/kitty.css +61 -0
  214. package/initramfs/buninu/apps/jsgotty/static/css/xterm.css +218 -0
  215. package/initramfs/buninu/apps/jsgotty/static/css/xterm_customize.css +35 -0
  216. package/initramfs/buninu/apps/jsgotty/static/favicon.ico +0 -0
  217. package/initramfs/buninu/apps/jsgotty/static/help.md +125 -0
  218. package/initramfs/buninu/apps/jsgotty/static/icon.svg +66 -0
  219. package/initramfs/buninu/apps/jsgotty/static/icon_192.png +0 -0
  220. package/initramfs/buninu/apps/jsgotty/static/index.html +164 -0
  221. package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +31537 -0
  222. package/initramfs/buninu/apps/jsgotty/static/js/gotty.js.LICENSE.txt +7 -0
  223. package/initramfs/buninu/apps/jsgotty/static/js/gotty.js.map +1 -0
  224. package/initramfs/buninu/apps/jsgotty/static/js/gotty.licenses.txt +650 -0
  225. package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +1101 -0
  226. package/initramfs/buninu/apps/jsgotty/static/js/windows_bridge.js +194 -0
  227. package/initramfs/buninu/apps/jsgotty/static/js/xterm-addon-unicode11.js +2 -0
  228. package/initramfs/buninu/apps/jsgotty/static/js/zmodem.js +1 -0
  229. package/initramfs/buninu/apps/jsgotty/static/manifest.json +15 -0
  230. package/initramfs/buninu/apps/jsgotty/sz.js +273 -0
  231. package/initramfs/buninu/apps/jsgotty/tests/kitty-order.test.js +58 -0
  232. package/initramfs/buninu/apps/jsgotty/tests/websocket-origin.test.js +54 -0
  233. package/initramfs/buninu/apps/jsgotty/viu.mjs +422 -0
  234. package/initramfs/buninu/apps/jsgotty/wgotty +62 -0
  235. package/initramfs/buninu/apps/jsgotty/windows-bridge-node.js +60 -0
  236. package/initramfs/buninu/apps/jsgotty/zmodem-node.js +262 -0
  237. package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +1118 -0
  238. package/initramfs/buninu/apps/jsmdcui/LICENSE +22 -0
  239. package/initramfs/buninu/apps/jsmdcui/README.md +1283 -0
  240. package/initramfs/buninu/apps/jsmdcui/cdp-maze.js +141 -0
  241. package/initramfs/buninu/apps/jsmdcui/clean.sh +9 -0
  242. package/initramfs/buninu/apps/jsmdcui/demos/basic.jpg +0 -0
  243. package/initramfs/buninu/apps/jsmdcui/demos/chinese.md +32 -0
  244. package/initramfs/buninu/apps/jsmdcui/demos/event.md +170 -0
  245. package/initramfs/buninu/apps/jsmdcui/demos/good.jpg +0 -0
  246. package/initramfs/buninu/apps/jsmdcui/demos/image-processor.md +366 -0
  247. package/initramfs/buninu/apps/jsmdcui/demos/image-processor.zh-TW.md +366 -0
  248. package/initramfs/buninu/apps/jsmdcui/demos/img-change.md +73 -0
  249. package/initramfs/buninu/apps/jsmdcui/demos/imgtool-zh.md +298 -0
  250. package/initramfs/buninu/apps/jsmdcui/demos/imgtool.md +298 -0
  251. package/initramfs/buninu/apps/jsmdcui/demos/maze.md +149 -0
  252. package/initramfs/buninu/apps/jsmdcui/demos/reactive.md +40 -0
  253. package/initramfs/buninu/apps/jsmdcui/demos/reader.md +513 -0
  254. package/initramfs/buninu/apps/jsmdcui/demos/select.md +63 -0
  255. package/initramfs/buninu/apps/jsmdcui/demos/todo-zh.md +190 -0
  256. package/initramfs/buninu/apps/jsmdcui/demos/todo.md +191 -0
  257. package/initramfs/buninu/apps/jsmdcui/edit +9 -0
  258. package/initramfs/buninu/apps/jsmdcui/jsmdcui.sh +16 -0
  259. package/initramfs/buninu/apps/jsmdcui/llm-maze.txt +94 -0
  260. package/initramfs/buninu/apps/jsmdcui/package.json +29 -0
  261. package/initramfs/buninu/apps/jsmdcui/project_structure.txt +35 -0
  262. package/initramfs/buninu/apps/jsmdcui/runmd.mjs +1140 -0
  263. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/atom-dark.micro +33 -0
  264. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/bubblegum.micro +31 -0
  265. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/cmc-16.micro +47 -0
  266. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/cmc-tc.micro +43 -0
  267. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/darcula.micro +34 -0
  268. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/default.micro +1 -0
  269. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dracula-tc.micro +49 -0
  270. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukedark-tc.micro +38 -0
  271. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukelight-tc.micro +38 -0
  272. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
  273. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/geany.micro +29 -0
  274. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gotham.micro +29 -0
  275. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gruvbox-tc.micro +29 -0
  276. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gruvbox.micro +26 -0
  277. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/material-tc.micro +36 -0
  278. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/monokai-dark.micro +28 -0
  279. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/monokai.micro +34 -0
  280. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/one-dark.micro +39 -0
  281. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/railscast.micro +37 -0
  282. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/simple.micro +33 -0
  283. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/solarized-tc.micro +31 -0
  284. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/solarized.micro +30 -0
  285. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/sunny-day.micro +29 -0
  286. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/twilight.micro +40 -0
  287. package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/zenburn.micro +30 -0
  288. package/initramfs/buninu/apps/jsmdcui/runtime/help/actions.md +161 -0
  289. package/initramfs/buninu/apps/jsmdcui/runtime/help/cdp.md +130 -0
  290. package/initramfs/buninu/apps/jsmdcui/runtime/help/colors.md +421 -0
  291. package/initramfs/buninu/apps/jsmdcui/runtime/help/commands.md +161 -0
  292. package/initramfs/buninu/apps/jsmdcui/runtime/help/copypaste.md +149 -0
  293. package/initramfs/buninu/apps/jsmdcui/runtime/help/defaultkeys.md +148 -0
  294. package/initramfs/buninu/apps/jsmdcui/runtime/help/help.md +1283 -0
  295. package/initramfs/buninu/apps/jsmdcui/runtime/help/keybindings.md +355 -0
  296. package/initramfs/buninu/apps/jsmdcui/runtime/help/linter.md +90 -0
  297. package/initramfs/buninu/apps/jsmdcui/runtime/help/options.md +709 -0
  298. package/initramfs/buninu/apps/jsmdcui/runtime/help/plugins.md +544 -0
  299. package/initramfs/buninu/apps/jsmdcui/runtime/help/tutorial.md +112 -0
  300. package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/cdp/cdp-server.js +1263 -0
  301. package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/cdp/cdp.js +203 -0
  302. package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/chapter/chapter.js +111 -0
  303. package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/diff/diff.js +46 -0
  304. package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/example/example.js +117 -0
  305. package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/linter/linter.js +281 -0
  306. package/initramfs/buninu/apps/jsmdcui/runtime/plugins/autoclose/autoclose.lua +75 -0
  307. package/initramfs/buninu/apps/jsmdcui/runtime/plugins/ftoptions/ftoptions.lua +17 -0
  308. package/initramfs/buninu/apps/jsmdcui/runtime/plugins/literate/README.md +5 -0
  309. package/initramfs/buninu/apps/jsmdcui/runtime/plugins/literate/literate.lua +55 -0
  310. package/initramfs/buninu/apps/jsmdcui/runtime/plugins/status/help/status.md +21 -0
  311. package/initramfs/buninu/apps/jsmdcui/runtime/plugins/status/status.lua +62 -0
  312. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/LICENSE +22 -0
  313. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/PowerShell.yaml +128 -0
  314. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/README.md +63 -0
  315. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ada.yaml +43 -0
  316. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/apacheconf.yaml +59 -0
  317. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/arduino.yaml +101 -0
  318. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/asciidoc.yaml +51 -0
  319. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/asm.yaml +123 -0
  320. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ats.yaml +99 -0
  321. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/awk.yaml +44 -0
  322. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/b.yaml +87 -0
  323. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/bat.yaml +57 -0
  324. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/c.yaml +60 -0
  325. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/caddyfile.yaml +23 -0
  326. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cake.yaml +7 -0
  327. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/clojure.yaml +38 -0
  328. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cmake.yaml +42 -0
  329. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/coffeescript.yaml +56 -0
  330. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/colortest.yaml +19 -0
  331. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/conky.yaml +17 -0
  332. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cpp.yaml +91 -0
  333. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/crontab.yaml +36 -0
  334. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/crystal.yaml +72 -0
  335. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/csharp.yaml +52 -0
  336. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/css.yaml +44 -0
  337. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/csx.yaml +8 -0
  338. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cuda.yaml +68 -0
  339. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cython.yaml +52 -0
  340. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/d.yaml +121 -0
  341. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dart.yaml +46 -0
  342. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/default.yaml +10 -0
  343. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dockerfile.yaml +36 -0
  344. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dot.yaml +29 -0
  345. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/elixir.yaml +30 -0
  346. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/elm.yaml +38 -0
  347. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/erb.yaml +42 -0
  348. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/erlang.yaml +45 -0
  349. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fish.yaml +48 -0
  350. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/forth.yaml +34 -0
  351. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fortran.yaml +64 -0
  352. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/freebsd-kernel.yaml +14 -0
  353. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fsharp.yaml +48 -0
  354. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gdscript.yaml +61 -0
  355. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gemini.yaml +19 -0
  356. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gentoo-ebuild.yaml +48 -0
  357. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gentoo-etc-portage.yaml +23 -0
  358. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-commit.yaml +35 -0
  359. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-config.yaml +14 -0
  360. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-rebase-todo.yaml +19 -0
  361. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gleam.yaml +69 -0
  362. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/glsl.yaml +26 -0
  363. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gnuplot.yaml +15 -0
  364. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/go.yaml +62 -0
  365. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/godoc.yaml +17 -0
  366. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/golo.yaml +73 -0
  367. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gomod.yaml +31 -0
  368. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/graphql.yaml +47 -0
  369. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/groff.yaml +30 -0
  370. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/groovy.yaml +111 -0
  371. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/haml.yaml +16 -0
  372. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/hare.yaml +52 -0
  373. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/haskell.yaml +52 -0
  374. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/hc.yaml +52 -0
  375. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html.yaml +70 -0
  376. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html4.yaml +25 -0
  377. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html5.yaml +25 -0
  378. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ini.yaml +23 -0
  379. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/inputrc.yaml +14 -0
  380. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/java.yaml +37 -0
  381. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/javascript.yaml +124 -0
  382. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/jinja2.yaml +19 -0
  383. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/json.yaml +39 -0
  384. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/jsonnet.yaml +92 -0
  385. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/julia.yaml +57 -0
  386. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/justfile.yaml +40 -0
  387. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/keymap.yaml +27 -0
  388. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kickstart.yaml +16 -0
  389. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kotlin.yaml +66 -0
  390. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kvlang.yaml +67 -0
  391. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ledger.yaml +14 -0
  392. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lfe.yaml +17 -0
  393. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lilypond.yaml +26 -0
  394. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lisp.yaml +17 -0
  395. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/log.yaml +92 -0
  396. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lua.yaml +111 -0
  397. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mail.yaml +25 -0
  398. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/makefile.yaml +38 -0
  399. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/man.yaml +12 -0
  400. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/markdown.yaml +65 -0
  401. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mc.yaml +23 -0
  402. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/meson.yaml +51 -0
  403. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/micro.yaml +34 -0
  404. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mpdconf.yaml +13 -0
  405. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/msbuild.yaml +6 -0
  406. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nanorc.yaml +16 -0
  407. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nftables.yaml +30 -0
  408. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nginx.yaml +22 -0
  409. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nim.yaml +27 -0
  410. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nix.yaml +32 -0
  411. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nu.yaml +114 -0
  412. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/objc.yaml +60 -0
  413. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ocaml.yaml +43 -0
  414. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/octave.yaml +83 -0
  415. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/odin.yaml +64 -0
  416. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pascal.yaml +45 -0
  417. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/patch.yaml +14 -0
  418. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/peg.yaml +16 -0
  419. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/perl.yaml +58 -0
  420. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/php.yaml +60 -0
  421. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pkg-config.yaml +12 -0
  422. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/po.yaml +12 -0
  423. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pony.yaml +37 -0
  424. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pov.yaml +21 -0
  425. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-action.yaml +14 -0
  426. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-config.yaml +10 -0
  427. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-filter.yaml +12 -0
  428. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/proto.yaml +40 -0
  429. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/prql.yaml +84 -0
  430. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/puppet.yaml +22 -0
  431. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/python2.yaml +60 -0
  432. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/python3.yaml +80 -0
  433. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/r.yaml +32 -0
  434. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/raku.yaml +42 -0
  435. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/reST.yaml +18 -0
  436. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/renpy.yaml +15 -0
  437. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/rpmspec.yaml +43 -0
  438. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ruby.yaml +73 -0
  439. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/rust.yaml +78 -0
  440. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sage.yaml +60 -0
  441. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/scad.yaml +53 -0
  442. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/scala.yaml +33 -0
  443. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sed.yaml +13 -0
  444. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sh.yaml +69 -0
  445. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sls.yaml +15 -0
  446. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/smalltalk.yaml +55 -0
  447. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/solidity.yaml +41 -0
  448. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sql.yaml +35 -0
  449. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/stata.yaml +67 -0
  450. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/svelte.yaml +27 -0
  451. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/swift.yaml +103 -0
  452. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/systemd.yaml +16 -0
  453. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/tcl.yaml +18 -0
  454. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/terraform.yaml +87 -0
  455. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/tex.yaml +32 -0
  456. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/toml.yaml +56 -0
  457. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/twig.yaml +55 -0
  458. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/typescript.yaml +49 -0
  459. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/v.yaml +80 -0
  460. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vala.yaml +26 -0
  461. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/verilog.yaml +60 -0
  462. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vhdl.yaml +37 -0
  463. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vi.yaml +31 -0
  464. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vue.yaml +64 -0
  465. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/xml.yaml +37 -0
  466. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/xresources.yaml +14 -0
  467. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/yaml.yaml +34 -0
  468. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/yum.yaml +12 -0
  469. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zig.yaml +52 -0
  470. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zscript.yaml +72 -0
  471. package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zsh.yaml +52 -0
  472. package/initramfs/buninu/apps/jsmdcui/single-exe/README.md +396 -0
  473. package/initramfs/buninu/apps/jsmdcui/single-exe/assetsHelper.js +371 -0
  474. package/initramfs/buninu/apps/jsmdcui/single-exe/assetsLoader.mjs +85 -0
  475. package/initramfs/buninu/apps/jsmdcui/single-exe/assetsPacker.js +218 -0
  476. package/initramfs/buninu/apps/jsmdcui/single-exe/compiled.js +498 -0
  477. package/initramfs/buninu/apps/jsmdcui/single-exe/entry.mjs +17 -0
  478. package/initramfs/buninu/apps/jsmdcui/single-exe/packAssets.sh +7 -0
  479. package/initramfs/buninu/apps/jsmdcui/src/MDCUI_DEFAULT_EDIT +0 -0
  480. package/initramfs/buninu/apps/jsmdcui/src/buffer/backup.js +165 -0
  481. package/initramfs/buninu/apps/jsmdcui/src/buffer/buffer.js +126 -0
  482. package/initramfs/buninu/apps/jsmdcui/src/buffer/fixed3-codec.js +140 -0
  483. package/initramfs/buninu/apps/jsmdcui/src/buffer/loc.js +38 -0
  484. package/initramfs/buninu/apps/jsmdcui/src/buffer/message.js +29 -0
  485. package/initramfs/buninu/apps/jsmdcui/src/build-args.js +40 -0
  486. package/initramfs/buninu/apps/jsmdcui/src/config/clean.js +172 -0
  487. package/initramfs/buninu/apps/jsmdcui/src/config/colorscheme.js +99 -0
  488. package/initramfs/buninu/apps/jsmdcui/src/config/config.js +125 -0
  489. package/initramfs/buninu/apps/jsmdcui/src/config/defaults.js +109 -0
  490. package/initramfs/buninu/apps/jsmdcui/src/cui/fence-events.mjs +144 -0
  491. package/initramfs/buninu/apps/jsmdcui/src/cui/heading-ids.mjs +1568 -0
  492. package/initramfs/buninu/apps/jsmdcui/src/cui/id-collision.mjs +111 -0
  493. package/initramfs/buninu/apps/jsmdcui/src/cui/identity.mjs +26 -0
  494. package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-debug.mjs +25 -0
  495. package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-images.mjs +280 -0
  496. package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-mode.mjs +6 -0
  497. package/initramfs/buninu/apps/jsmdcui/src/cui/rerender-queue.mjs +66 -0
  498. package/initramfs/buninu/apps/jsmdcui/src/cui/rpc.mjs +1554 -0
  499. package/initramfs/buninu/apps/jsmdcui/src/cui/server.mjs +128 -0
  500. package/initramfs/buninu/apps/jsmdcui/src/cui/table-render.mjs +286 -0
  501. package/initramfs/buninu/apps/jsmdcui/src/cui/table-row-edit.mjs +226 -0
  502. package/initramfs/buninu/apps/jsmdcui/src/cui/task-checkbox.mjs +60 -0
  503. package/initramfs/buninu/apps/jsmdcui/src/cui/template-components.mjs +253 -0
  504. package/initramfs/buninu/apps/jsmdcui/src/cui/text-block-resize.mjs +177 -0
  505. package/initramfs/buninu/apps/jsmdcui/src/cui/tui-links.mjs +560 -0
  506. package/initramfs/buninu/apps/jsmdcui/src/display/ansi-style.js +60 -0
  507. package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +243 -0
  508. package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +225 -0
  509. package/initramfs/buninu/apps/jsmdcui/src/index.js +9586 -0
  510. package/initramfs/buninu/apps/jsmdcui/src/lua/engine.js +68 -0
  511. package/initramfs/buninu/apps/jsmdcui/src/platform/archive.js +50 -0
  512. package/initramfs/buninu/apps/jsmdcui/src/platform/clipboard.js +278 -0
  513. package/initramfs/buninu/apps/jsmdcui/src/platform/commands.js +221 -0
  514. package/initramfs/buninu/apps/jsmdcui/src/platform/terminal.js +5 -0
  515. package/initramfs/buninu/apps/jsmdcui/src/plugins/js-bridge.js +3377 -0
  516. package/initramfs/buninu/apps/jsmdcui/src/plugins/manager.js +674 -0
  517. package/initramfs/buninu/apps/jsmdcui/src/runtime/encodings.js +10 -0
  518. package/initramfs/buninu/apps/jsmdcui/src/runtime/registry.js +134 -0
  519. package/initramfs/buninu/apps/jsmdcui/src/screen/cell-buffer.js +81 -0
  520. package/initramfs/buninu/apps/jsmdcui/src/screen/events.js +263 -0
  521. package/initramfs/buninu/apps/jsmdcui/src/screen/screen.js +249 -0
  522. package/initramfs/buninu/apps/jsmdcui/src/screen/vt100.js +571 -0
  523. package/initramfs/buninu/apps/jsmdcui/src/shell/shell.js +70 -0
  524. package/initramfs/buninu/apps/jsmdcui/testapp.md +78 -0
  525. package/initramfs/buninu/apps/jsmdcui/tests/assets-helper.test.js +150 -0
  526. package/initramfs/buninu/apps/jsmdcui/tests/backup.test.js +158 -0
  527. package/initramfs/buninu/apps/jsmdcui/tests/cat-markdown.test.js +139 -0
  528. package/initramfs/buninu/apps/jsmdcui/tests/cdp-security.test.js +32 -0
  529. package/initramfs/buninu/apps/jsmdcui/tests/cdp-websocket-test.html +46 -0
  530. package/initramfs/buninu/apps/jsmdcui/tests/cmphex3b64.js +95 -0
  531. package/initramfs/buninu/apps/jsmdcui/tests/compiled-runtime.test.js +139 -0
  532. package/initramfs/buninu/apps/jsmdcui/tests/config-settings.test.js +52 -0
  533. package/initramfs/buninu/apps/jsmdcui/tests/demo.test.js +448 -0
  534. package/initramfs/buninu/apps/jsmdcui/tests/docs-sync.test.js +14 -0
  535. package/initramfs/buninu/apps/jsmdcui/tests/fence-events.test.js +1293 -0
  536. package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +559 -0
  537. package/initramfs/buninu/apps/jsmdcui/tests/heading-list-selector.test.js +2393 -0
  538. package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +514 -0
  539. package/initramfs/buninu/apps/jsmdcui/tests/jmigt.js +16 -0
  540. package/initramfs/buninu/apps/jsmdcui/tests/js-plugin-prompts.test.js +110 -0
  541. package/initramfs/buninu/apps/jsmdcui/tests/key-event.md +10 -0
  542. package/initramfs/buninu/apps/jsmdcui/tests/kitty-demo.md +184 -0
  543. package/initramfs/buninu/apps/jsmdcui/tests/kitty-images.test.js +288 -0
  544. package/initramfs/buninu/apps/jsmdcui/tests/kitty-mode.test.js +14 -0
  545. package/initramfs/buninu/apps/jsmdcui/tests/platform-commands.test.js +18 -0
  546. package/initramfs/buninu/apps/jsmdcui/tests/platform-terminal.test.js +8 -0
  547. package/initramfs/buninu/apps/jsmdcui/tests/pty-demo.js +492 -0
  548. package/initramfs/buninu/apps/jsmdcui/tests/rerender-queue.test.js +130 -0
  549. package/initramfs/buninu/apps/jsmdcui/tests/table-render.test.js +150 -0
  550. package/initramfs/buninu/apps/jsmdcui/tests/table-row-edit.test.js +138 -0
  551. package/initramfs/buninu/apps/jsmdcui/tests/task-checkbox.test.js +33 -0
  552. package/initramfs/buninu/apps/jsmdcui/tests/template-components.test.js +422 -0
  553. package/initramfs/buninu/apps/jsmdcui/tests/text-block-resize.test.js +222 -0
  554. package/initramfs/buninu/apps/jsmdcui/tests/textarea.md +8 -0
  555. package/initramfs/buninu/apps/jsmdcui/tests/tui-links.test.js +298 -0
  556. package/initramfs/buninu/apps/jsmdcui/tests/wui-responsive-images.test.js +60 -0
  557. package/initramfs/buninu/apps/jsmdcui/tests/wui.test.js +108 -0
  558. package/initramfs/buninu/apps/jsmdcui/tests/wv-client.js +96 -0
  559. package/initramfs/buninu/apps/jsmdcui/tui +7 -0
  560. package/initramfs/buninu/apps/jsmdcui/wui +9 -0
  561. package/initramfs/buninu/apps/musl-la/LICENSES/GPL-3.0.txt +674 -0
  562. package/initramfs/buninu/apps/musl-la/LICENSES/gcc_runtime_exception.txt +73 -0
  563. package/initramfs/buninu/apps/musl-la/LICENSE_musl.txt +193 -0
  564. package/initramfs/buninu/apps/musl-la/NOTICE +41 -0
  565. package/initramfs/buninu/apps/musl-la/ld-musl-aarch64.so.1 +0 -0
  566. package/initramfs/buninu/apps/musl-la/libgcc_s.so.1 +0 -0
  567. package/initramfs/buninu/apps/musl-la/libstdc++.so.6 +0 -0
  568. package/initramfs/buninu/apps/musl-la/musl-la.sh +36 -0
  569. package/initramfs/buninu/apps/native-bridge/native-bridge.js +199 -0
  570. package/initramfs/buninu/apps/native-bridge/package.json +8 -0
  571. package/initramfs/buninu/apps/tts/tts.js +214 -0
  572. package/initramfs/buninu/apps/xclip/xclip.js +121 -0
  573. package/initramfs/buninu/apps/xdg-open/xdg-open.js +151 -0
  574. package/initramfs/buninu/bin/bun.bat +37 -0
  575. package/initramfs/buninu/bin/bun.sh +57 -0
  576. package/initramfs/buninu/bin/buninu-help.bat +4 -0
  577. package/initramfs/buninu/bin/buninu-help.sh +7 -0
  578. package/initramfs/buninu/bin/bunmsh.bat +3 -0
  579. package/initramfs/buninu/bin/bunmsh.sh +5 -0
  580. package/initramfs/buninu/bin/bunx.bat +3 -0
  581. package/initramfs/buninu/bin/glow.bat +3 -0
  582. package/initramfs/buninu/bin/glow.sh +5 -0
  583. package/initramfs/buninu/bin/init.js +779 -0
  584. package/initramfs/buninu/bin/install.js +570 -0
  585. package/initramfs/buninu/bin/jmi.bat +3 -0
  586. package/initramfs/buninu/bin/jmi.sh +5 -0
  587. package/initramfs/buninu/bin/jsgotty.bat +3 -0
  588. package/initramfs/buninu/bin/jsgotty.sh +5 -0
  589. package/initramfs/buninu/bin/jsmdcui.bat +11 -0
  590. package/initramfs/buninu/bin/libsh-loader.c +135 -0
  591. package/initramfs/buninu/bin/libsh-loader.so +0 -0
  592. package/initramfs/buninu/bin/multicall.sh +103 -0
  593. package/initramfs/buninu/bin/native-bridge.bat +3 -0
  594. package/initramfs/buninu/bin/rz.bat +3 -0
  595. package/initramfs/buninu/bin/rz.sh +5 -0
  596. package/initramfs/buninu/bin/shc.js +5 -0
  597. package/initramfs/buninu/bin/showimg.bat +3 -0
  598. package/initramfs/buninu/bin/showimg.sh +5 -0
  599. package/initramfs/buninu/bin/switch_to_android.sh +9 -0
  600. package/initramfs/buninu/bin/switch_to_linux.sh +7 -0
  601. package/initramfs/buninu/bin/sz.bat +3 -0
  602. package/initramfs/buninu/bin/sz.sh +5 -0
  603. package/initramfs/buninu/bin/tts.bat +3 -0
  604. package/initramfs/buninu/bin/xclip.bat +3 -0
  605. package/initramfs/buninu/bin/xdg-open.bat +3 -0
  606. package/initramfs/buninu/demo-server.js +64 -0
  607. package/initramfs/buninu/icon.png +0 -0
  608. package/initramfs/buninu/package.json +59 -0
  609. package/initramfs/etc/resolv.conf +1 -0
  610. package/initramfs/init.js +339 -0
  611. package/initramfs/lib/ld-musl-x86_64.so.1 +0 -0
  612. package/initramfs/lib/libc.musl-x86_64.so.1 +0 -0
  613. package/initramfs/lib/libgcc_s.so.1 +0 -0
  614. package/initramfs/lib/libstdc++.so.6 +0 -0
  615. package/initramfs/usr/bin/env +7 -0
  616. package/package.json +42 -0
  617. package/run-qemu.sh +44 -0
  618. package/scripts/fetch.sh +26 -0
  619. package/scripts/pack-initramfs.sh +41 -0
  620. package/test/build-init-bootstrap.sh +23 -0
  621. package/test/init-bootstrap.c +55 -0
@@ -0,0 +1,2393 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { createWebDollar } from "../src/cui/rpc.mjs";
3
+ import {
4
+ addTuiTableRowSeparators,
5
+ markHeadingTableRows,
6
+ } from "../src/cui/table-render.mjs";
7
+ import {
8
+ captureTuiRerenderState,
9
+ clearTuiSourceDependentState,
10
+ createTuiSelector,
11
+ indexTuiHeadingRows,
12
+ markTuiTableCellDirtyAtPosition,
13
+ restoreTuiRerenderState,
14
+ spliceTuiBufferLines,
15
+ toggleTuiHeadingAt,
16
+ tuiTableCellAtPosition,
17
+ tuiCheckboxRerenderMismatchMessage,
18
+ } from "../src/plugins/js-bridge.js";
19
+ import { colorAnsiPlainRange } from "../src/cui/table-row-edit.mjs";
20
+
21
+ function tuiSelector(markdown) {
22
+ const ansi = String(Bun.markdown.ansi(markdown, { hyperlinks: true, columns: 80 }));
23
+ const buffer = {
24
+ lines: Bun.stripANSI(ansi).split("\n"),
25
+ _mdcuiTuiSourceText: markdown,
26
+ _mdcuiAnsiText: ansi,
27
+ cursor: { x: 0, y: 0 },
28
+ ensureCursor() {},
29
+ invalidateHighlightFrom() {},
30
+ };
31
+ indexTuiHeadingRows(buffer);
32
+ return { $: createTuiSelector(() => buffer), buffer };
33
+ }
34
+
35
+ function tuiTableSelector(markdown, columns = 24) {
36
+ const plan = markHeadingTableRows(markdown);
37
+ const rendered = String(Bun.markdown.ansi(
38
+ plan.markdown,
39
+ { hyperlinks: true, columns },
40
+ ));
41
+ const ansi = addTuiTableRowSeparators(rendered, plan);
42
+ const buffer = {
43
+ lines: Bun.stripANSI(ansi).split("\n"),
44
+ _mdcuiTuiSourceText: markdown,
45
+ _mdcuiAnsiText: ansi,
46
+ _ansiStyleLines: Bun.stripANSI(ansi).split("\n").map(line =>
47
+ Array.from({ length: line.length }, () => ({ fg: "white" }))
48
+ ),
49
+ cursor: { x: 0, y: 0 },
50
+ ensureCursor() {},
51
+ invalidateHighlightFrom() {},
52
+ pushUndo() {
53
+ this.undoCount = (this.undoCount ?? 0) + 1;
54
+ },
55
+ };
56
+ indexTuiHeadingRows(buffer);
57
+ return { $: createTuiSelector(() => buffer), buffer };
58
+ }
59
+
60
+ const markdown = `## Features
61
+
62
+ - [x] Search
63
+ - [ ] Notifications
64
+ - [x] Nested notification option
65
+ - [x] Offline
66
+
67
+ Paragraph between lists.
68
+
69
+ - [x] Later list item
70
+
71
+ ## Next
72
+ `;
73
+
74
+ describe("TUI heading task-list Array methods", () => {
75
+ test("push and unshift accept multiple items and return the new direct length", () => {
76
+ const { $ } = tuiSelector(markdown);
77
+ const features = $("#features");
78
+
79
+ expect(features.push("Export", { value: "Sync", checked: true })).toBe(5);
80
+ expect(features.val()).toEqual(["Search", "Offline", "Sync"]);
81
+ expect(features.unshift("First", { label: "Pinned", checked: true })).toBe(7);
82
+ expect(features.val()).toEqual(["Pinned", "Search", "Offline", "Sync"]);
83
+ });
84
+
85
+ test("pop and shift return removed labels and remove nested children with the parent", () => {
86
+ const { $, buffer } = tuiSelector(markdown);
87
+ const features = $("#features");
88
+
89
+ expect(features.shift()).toBe("Search");
90
+ expect(buffer.lines.some((line) => line.includes("Search"))).toBe(false);
91
+ expect(features.shift()).toBe("Notifications");
92
+ expect(buffer.lines.some((line) => line.includes("Nested notification option"))).toBe(false);
93
+ expect(features.pop()).toBe("Offline");
94
+ expect(features.pop()).toBeUndefined();
95
+ });
96
+
97
+ test("an emptied list keeps its insertion point for later pushes", () => {
98
+ const { $ } = tuiSelector("## Features\n\n- [ ] Only\n\n## Next\n");
99
+ const features = $("#features");
100
+
101
+ expect(features.pop()).toBe("Only");
102
+ expect(features.pop()).toBeUndefined();
103
+ expect(features.push({ value: "Again", checked: true })).toBe(1);
104
+ expect(features.val()).toEqual(["Again"]);
105
+ });
106
+
107
+ test("an empty child list keeps its position across block resizing and ancestor visibility", () => {
108
+ const scenarios = [
109
+ { tag: "text", value: "one\ntwo\nthree\nfour", label: "After growing text" },
110
+ { tag: "textarea", value: "one", label: "After shrinking textarea" },
111
+ ];
112
+
113
+ for (const scenario of scenarios) {
114
+ const source = `# Parent
115
+
116
+ \`\`\`${scenario.tag}#editor
117
+ one
118
+ two
119
+ \`\`\`
120
+
121
+ ## Child
122
+
123
+ - [ ] Only child item
124
+
125
+ ## Later
126
+
127
+ Later body.
128
+
129
+ # End
130
+ `;
131
+ const { $, buffer } = tuiSelector(source);
132
+ const child = $("#child");
133
+
134
+ expect(child.pop()).toBe("Only child item");
135
+ $(`${scenario.tag}#editor`).val(scenario.value);
136
+ $("#parent").hide().show();
137
+
138
+ expect(child.push(scenario.label)).toBe(1);
139
+
140
+ const childHeading = buffer.lines.findIndex((line) => line.trim() === "Child");
141
+ const inserted = buffer.lines.findIndex((line) => line.includes(scenario.label));
142
+ const laterHeading = buffer.lines.findIndex((line) => line.trim() === "Later");
143
+ expect(inserted).toBeGreaterThan(childHeading);
144
+ expect(inserted).toBeLessThan(laterHeading);
145
+ expect(child.slice()).toEqual([{ value: scenario.label, checked: false }]);
146
+ }
147
+ });
148
+
149
+ test("a hidden section keeps relative empty-list anchors when a preceding block grows", () => {
150
+ const source = `\`\`\`text#before
151
+ one
152
+ \`\`\`
153
+
154
+ # Parent
155
+
156
+ ## Child
157
+
158
+ - [ ] Only child item
159
+
160
+ # End
161
+ `;
162
+ const { $, buffer } = tuiSelector(source);
163
+ const child = $("#child");
164
+
165
+ expect(child.pop()).toBe("Only child item");
166
+ $("#parent").hide();
167
+ $("text#before").val("one\ntwo\nthree\nfour");
168
+ $("#parent").show();
169
+ expect(child.push("After hidden growth")).toBe(1);
170
+
171
+ const childHeading = buffer.lines.findIndex((line) => line.trim() === "Child");
172
+ const inserted = buffer.lines.findIndex((line) => line.includes("After hidden growth"));
173
+ const endHeading = buffer.lines.findIndex((line) => line.trim() === "End");
174
+ expect(inserted).toBeGreaterThan(childHeading);
175
+ expect(inserted).toBeLessThan(endHeading);
176
+ expect(child.slice()).toEqual([{ value: "After hidden growth", checked: false }]);
177
+ });
178
+
179
+ test("nested hidden sections restore every empty-list anchor independently", () => {
180
+ const source = `# Parent
181
+
182
+ ## Alpha
183
+
184
+ - [ ] Only alpha
185
+
186
+ ### Deep
187
+
188
+ - [ ] Only deep
189
+
190
+ ## Beta
191
+
192
+ - [ ] Only beta
193
+
194
+ # End
195
+ `;
196
+ const { $, buffer } = tuiSelector(source);
197
+ const alpha = $("#alpha");
198
+ const deep = $("#deep");
199
+ const beta = $("#beta");
200
+
201
+ expect(alpha.pop()).toBe("Only alpha");
202
+ expect(deep.pop()).toBe("Only deep");
203
+ expect(beta.pop()).toBe("Only beta");
204
+
205
+ alpha.hide();
206
+ $("#parent").hide().show();
207
+ alpha.show();
208
+
209
+ expect(alpha.push("Restored alpha")).toBe(1);
210
+ expect(deep.push("Restored deep")).toBe(1);
211
+ expect(beta.push("Restored beta")).toBe(1);
212
+ expect(alpha.slice()).toEqual([{ value: "Restored alpha", checked: false }]);
213
+ expect(deep.slice()).toEqual([{ value: "Restored deep", checked: false }]);
214
+ expect(beta.slice()).toEqual([{ value: "Restored beta", checked: false }]);
215
+
216
+ const positions = Object.fromEntries(
217
+ ["Alpha", "Restored alpha", "Deep", "Restored deep", "Beta", "Restored beta", "End"]
218
+ .map((text) => [text, buffer.lines.findIndex((line) => line.includes(text))]),
219
+ );
220
+ expect(positions.Alpha).toBeLessThan(positions["Restored alpha"]);
221
+ expect(positions["Restored alpha"]).toBeLessThan(positions.Deep);
222
+ expect(positions.Deep).toBeLessThan(positions["Restored deep"]);
223
+ expect(positions["Restored deep"]).toBeLessThan(positions.Beta);
224
+ expect(positions.Beta).toBeLessThan(positions["Restored beta"]);
225
+ expect(positions["Restored beta"]).toBeLessThan(positions.End);
226
+ });
227
+
228
+ test("val and mutations stop at the first rendered task list", () => {
229
+ const { $ } = tuiSelector(markdown);
230
+ const features = $("#features");
231
+
232
+ expect(features.val()).toEqual(["Search", "Offline"]);
233
+ expect(features.pop()).toBe("Offline");
234
+ expect(features.val()).toEqual(["Search"]);
235
+ });
236
+
237
+ test("a loose task list remains one list across indented item content", () => {
238
+ const { $ } = tuiSelector(`## Features
239
+
240
+ - [x] First
241
+
242
+ More detail
243
+
244
+ - [x] Second
245
+
246
+ Paragraph after the list.
247
+
248
+ - [x] Later list
249
+ `);
250
+ const features = $("#features");
251
+
252
+ expect(features.val()).toEqual(["First", "Second"]);
253
+ expect(features.pop()).toBe("Second");
254
+ expect(features.pop()).toBe("First");
255
+ expect(features.pop()).toBeUndefined();
256
+ });
257
+
258
+ test("splice follows Array indexing, insertion, deletion, and return semantics", () => {
259
+ const { $, buffer } = tuiSelector(markdown);
260
+ const features = $("#features");
261
+
262
+ expect(features.splice()).toEqual([]);
263
+ expect(features.splice(1, 1, { value: "Replacement", checked: true }, "Extra"))
264
+ .toEqual(["Notifications"]);
265
+ expect(buffer.lines.some((line) => line.includes("Nested notification option"))).toBe(false);
266
+ expect(features.val()).toEqual(["Search", "Replacement", "Offline"]);
267
+ expect(features.splice(-1)).toEqual(["Offline"]);
268
+ expect(features.splice(1, undefined, "Inserted")).toEqual([]);
269
+ expect(features.splice(0, Infinity)).toEqual(["Search", "Inserted", "Replacement", "Extra"]);
270
+ expect(features.val()).toEqual([]);
271
+ expect(features.splice(0, 0, { label: "Again", checked: true })).toEqual([]);
272
+ expect(features.val()).toEqual(["Again"]);
273
+ });
274
+
275
+ test("slice returns read-only item snapshots with checked state", () => {
276
+ const { $ } = tuiSelector(markdown);
277
+ const features = $("#features");
278
+
279
+ const result = features.slice(0, 2);
280
+ expect(result).toEqual([
281
+ { value: "Search", checked: true },
282
+ { value: "Notifications", checked: false },
283
+ ]);
284
+ result[0].value = "Changed snapshot";
285
+ result[0].checked = false;
286
+ expect(features.slice(-1)).toEqual([{ value: "Offline", checked: true }]);
287
+ expect(features.val()).toEqual(["Search", "Offline"]);
288
+ });
289
+
290
+ test("a heading without a task list cannot create one implicitly", () => {
291
+ const { $, buffer } = tuiSelector("## Empty\n\nParagraph.\n");
292
+ expect($("#empty").push("No target")).toBe(0);
293
+ expect($("#empty").unshift("No target")).toBe(0);
294
+ expect($("#empty").pop()).toBeUndefined();
295
+ expect($("#empty").shift()).toBeUndefined();
296
+ expect(buffer._mdcuiMutationMacros).toBeUndefined();
297
+ });
298
+
299
+ test("does not borrow a task list from outside the heading's Markdown container", () => {
300
+ const scenarios = [
301
+ {
302
+ source: `> ## Quoted
303
+ >
304
+ > Inside quote.
305
+
306
+ - [x] Outside quote
307
+ `,
308
+ id: "quoted",
309
+ outside: "Outside quote",
310
+ },
311
+ {
312
+ source: `- Item
313
+
314
+ ## Listed
315
+
316
+ Inside list item.
317
+
318
+ - [x] Outside list item
319
+ `,
320
+ id: "listed",
321
+ outside: "Outside list item",
322
+ },
323
+ ];
324
+
325
+ for (const scenario of scenarios) {
326
+ const { $, buffer } = tuiSelector(scenario.source);
327
+ const heading = $(`#${scenario.id}`);
328
+
329
+ expect(heading.val()).toEqual([]);
330
+ expect(heading.slice()).toEqual([]);
331
+ expect(heading.push("Must stay absent")).toBe(0);
332
+ expect(buffer.lines.some(line => line.includes(scenario.outside)))
333
+ .toBe(true);
334
+ expect(buffer.lines.some(line => line.includes("Must stay absent")))
335
+ .toBe(false);
336
+ }
337
+ });
338
+
339
+ test("uses and mutates a direct task list inside a blockquote", () => {
340
+ const source = `> ## Quoted
341
+ >
342
+ > - [x] Inside checked
343
+ > - [ ] Inside unchecked
344
+
345
+ - [x] Outside quote
346
+ `;
347
+ const { $, buffer } = tuiSelector(source);
348
+ const quoted = $("#quoted");
349
+
350
+ expect(quoted.val()).toEqual(["Inside checked"]);
351
+ expect(quoted.slice()).toEqual([
352
+ { value: "Inside checked", checked: true },
353
+ { value: "Inside unchecked", checked: false },
354
+ ]);
355
+ expect(quoted.push({ value: "Inside added", checked: true })).toBe(3);
356
+ expect(quoted.val()).toEqual(["Inside checked", "Inside added"]);
357
+ expect(buffer.lines.some(line =>
358
+ line.startsWith("│") && line.includes("Inside added")
359
+ )).toBe(true);
360
+ expect(buffer.lines.some(line =>
361
+ line.includes("Outside quote") && line.includes("☒")
362
+ )).toBe(true);
363
+ });
364
+
365
+ test("does not mistake a blockquote table cell for a task-list item", () => {
366
+ const source = `> ## Quoted
367
+ >
368
+ > | Value |
369
+ > | --- |
370
+ > | ☒ Not a task |
371
+ >
372
+ > - [x] Real task
373
+ `;
374
+ const { $ } = tuiSelector(source);
375
+
376
+ expect($("#quoted").val()).toEqual(["Real task"]);
377
+ expect($("#quoted").slice()).toEqual([
378
+ { value: "Real task", checked: true },
379
+ ]);
380
+ });
381
+
382
+ test("mutations against a hidden heading fail without recording a macro", () => {
383
+ const { $, buffer } = tuiSelector(markdown);
384
+ const features = $("#features");
385
+
386
+ features.hide();
387
+ expect(features.push("Hidden addition")).toBe(0);
388
+ expect(features.pop()).toBeUndefined();
389
+ expect(features.shift()).toBeUndefined();
390
+ expect(features.unshift("Hidden first")).toBe(0);
391
+ expect(features.splice(0, 1, "Hidden replacement")).toEqual([]);
392
+ expect(buffer._mdcuiMutationMacros).toBeUndefined();
393
+
394
+ features.show();
395
+ expect(buffer.lines.some((line) => line.includes("Hidden addition"))).toBe(false);
396
+ expect(features.val()).toEqual(["Search", "Offline"]);
397
+ });
398
+
399
+ test("a preceding hidden section does not extend a later heading into its child list", () => {
400
+ const source = `## Earlier
401
+
402
+ ### Earlier child
403
+
404
+ Earlier body.
405
+
406
+ ## Parent
407
+
408
+ Parent has no list.
409
+
410
+ ### Child
411
+
412
+ - [x] Child checkbox
413
+ `;
414
+ const { $, buffer } = tuiSelector(source);
415
+
416
+ $("#earlier").hide();
417
+ expect($("#parent").val()).toEqual([]);
418
+ expect($("#parent").slice()).toEqual([]);
419
+ expect($("#parent").push("Must not reach child")).toBe(0);
420
+ expect(buffer._mdcuiMutationMacros).toBeUndefined();
421
+ expect($("#child").val()).toEqual(["Child checkbox"]);
422
+ expect(buffer.lines.some((line) => line.includes("Must not reach child"))).toBe(false);
423
+ });
424
+
425
+ test("a heading concealed by its parent cannot resolve to an earlier same-level heading", () => {
426
+ const source = `## Wrong
427
+
428
+ - [x] Wrong item
429
+
430
+ # Parent
431
+
432
+ ## Hidden
433
+
434
+ - [x] Hidden item
435
+
436
+ ## Also hidden
437
+
438
+ Body.
439
+
440
+ # End
441
+ `;
442
+ const { $, buffer } = tuiSelector(source);
443
+ const hidden = $("#hidden");
444
+
445
+ $("#parent").hide();
446
+ expect(hidden.line()).toBe(0);
447
+ expect(hidden.text()).toBe("");
448
+ expect(hidden.val()).toEqual([]);
449
+ expect(hidden.slice()).toEqual([]);
450
+ expect(hidden.push("Must not reach Wrong")).toBe(0);
451
+ expect(hidden.pop()).toBeUndefined();
452
+ expect(hidden.shift()).toBeUndefined();
453
+ expect(hidden.unshift("Must not reach Wrong first")).toBe(0);
454
+ expect(hidden.splice(0, 1, "Must not replace Wrong")).toEqual([]);
455
+ expect($("#wrong").slice()).toEqual([{ value: "Wrong item", checked: true }]);
456
+ expect(buffer._mdcuiMutationMacros).toBeUndefined();
457
+ expect(buffer.lines.some((line) => line.includes("Must not"))).toBe(false);
458
+ });
459
+ });
460
+
461
+ describe("TUI heading section visibility", () => {
462
+ const sectionMarkdown = `# First
463
+
464
+ First body.
465
+
466
+ ## Child
467
+
468
+ Child body.
469
+
470
+ ### Grandchild
471
+
472
+ Grandchild body.
473
+
474
+ ## Sibling
475
+
476
+ Sibling body.
477
+
478
+ # Second
479
+
480
+ Second body.
481
+ `;
482
+
483
+ test("hide keeps the heading and hides through the next equal or higher heading", () => {
484
+ const { $, buffer } = tuiSelector(sectionMarkdown);
485
+ const child = $("#child");
486
+
487
+ expect(child.hide()).toBe(child);
488
+ expect(buffer.lines.some(line => line.includes("Child"))).toBe(true);
489
+ expect(buffer.lines.some(line => line.includes("Child body."))).toBe(false);
490
+ expect(buffer.lines.some(line => line.includes("Grandchild"))).toBe(false);
491
+ expect(buffer.lines.some(line => line.includes("Sibling body."))).toBe(true);
492
+ expect(buffer.lines.some(line => line.includes("Second body."))).toBe(true);
493
+ });
494
+
495
+ test("show restores the section and toggle switches the id-centered state", () => {
496
+ const { $, buffer } = tuiSelector(sectionMarkdown);
497
+ const child = $("#child");
498
+
499
+ child.hide();
500
+ child.show();
501
+ expect(buffer.lines.some(line => line.includes("Child body."))).toBe(true);
502
+ expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(true);
503
+
504
+ child.toggle();
505
+ expect(buffer.lines.some(line => line.includes("Child body."))).toBe(false);
506
+ child.toggle();
507
+ expect(buffer.lines.some(line => line.includes("Child body."))).toBe(true);
508
+ });
509
+
510
+ test("show, hide, and toggle update a child while its parent is hidden", () => {
511
+ const scenarios = [
512
+ { initiallyHidden: false, method: "hide", visible: false },
513
+ { initiallyHidden: true, method: "show", visible: true },
514
+ { initiallyHidden: false, method: "toggle", visible: false },
515
+ { initiallyHidden: true, method: "toggle", visible: true },
516
+ ];
517
+
518
+ for (const scenario of scenarios) {
519
+ const { $, buffer } = tuiSelector(sectionMarkdown);
520
+ const first = $("#first");
521
+ const child = $("#child");
522
+ if (scenario.initiallyHidden) child.hide();
523
+ first.hide();
524
+
525
+ child[scenario.method]();
526
+
527
+ expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
528
+ expect(buffer.lines.some(line => line.includes("First body."))).toBe(false);
529
+ first.show();
530
+ expect(buffer.lines.some(line => line.includes("Child body.")))
531
+ .toBe(scenario.visible);
532
+ expect(Boolean(buffer._mdcuiIdStore.get("child")?.headingVisibility?.hidden))
533
+ .toBe(!scenario.visible);
534
+ }
535
+ });
536
+
537
+ test("a deep toggle crosses multiple hidden ancestors without exposing them", () => {
538
+ const { $, buffer } = tuiSelector(sectionMarkdown);
539
+ const first = $("#first");
540
+ const child = $("#child");
541
+ const grandchild = $("#grandchild");
542
+
543
+ $("#second").hide();
544
+ child.hide();
545
+ first.hide();
546
+ buffer.cursor = { x: 0, y: buffer.lines.length - 1 };
547
+ buffer.modified = false;
548
+ const cursor = { ...buffer.cursor };
549
+
550
+ grandchild.toggle();
551
+
552
+ expect(buffer.cursor).toEqual(cursor);
553
+ expect(buffer.modified).toBe(false);
554
+ expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
555
+ expect(buffer._mdcuiIdStore.get("child").headingVisibility.hidden).toBe(true);
556
+ expect(buffer._mdcuiIdStore.get("grandchild").headingVisibility.hidden).toBe(true);
557
+ expect(buffer._mdcuiIdStore.get("second").headingVisibility.hidden).toBe(true);
558
+
559
+ first.show();
560
+ child.show();
561
+ expect(buffer.lines.some(line => line.includes("Grandchild"))).toBe(true);
562
+ expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(false);
563
+ grandchild.show();
564
+ expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(true);
565
+ });
566
+
567
+ test("visibility changes do not mark the read-only rendered buffer modified", () => {
568
+ const { $, buffer } = tuiSelector(sectionMarkdown);
569
+ buffer.modified = false;
570
+
571
+ $("#first").hide();
572
+ expect(buffer.modified).toBe(false);
573
+ expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
574
+ $("#first").show();
575
+ expect(buffer.modified).toBe(false);
576
+ expect(buffer._mdcuiIdStore.has("first")).toBe(false);
577
+ });
578
+
579
+ test("heading visibility shares a general id record without replacing other state", () => {
580
+ const { $, buffer } = tuiSelector(sectionMarkdown);
581
+ const shared = { customState: { count: 1 } };
582
+ buffer._mdcuiIdStore = new Map([["child", shared]]);
583
+
584
+ $("#child").hide();
585
+ expect(buffer._mdcuiIdStore.get("child").customState).toEqual({ count: 1 });
586
+ expect(buffer._mdcuiIdStore.get("child").headingVisibility.hidden).toBe(true);
587
+ $("#child").show();
588
+ expect(buffer._mdcuiIdStore.get("child")).toBe(shared);
589
+ expect(buffer._mdcuiIdStore.get("child").headingVisibility).toBeUndefined();
590
+ });
591
+
592
+ test("a collapsed earlier section does not break a later heading reference", () => {
593
+ const { $, buffer } = tuiSelector(sectionMarkdown);
594
+
595
+ $("#child").hide();
596
+ $("#sibling").hide();
597
+ expect(buffer.lines.some(line => line.includes("Sibling"))).toBe(true);
598
+ expect(buffer.lines.some(line => line.includes("Sibling body."))).toBe(false);
599
+
600
+ $("#sibling").show();
601
+ expect(buffer.lines.some(line => line.includes("Sibling body."))).toBe(true);
602
+ expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(false);
603
+ });
604
+
605
+ test("show resolves the heading again after preceding text changes the line count", () => {
606
+ const { $, buffer } = tuiSelector(sectionMarkdown);
607
+ const sibling = $("#sibling");
608
+
609
+ sibling.hide();
610
+ spliceTuiBufferLines(buffer, 0, 0, ["Inserted one", "Inserted two"], {
611
+ ansi: ["Inserted one", "Inserted two"],
612
+ });
613
+ sibling.show();
614
+
615
+ const headingIndex = buffer.lines.findIndex(line => line.includes("Sibling"));
616
+ expect(headingIndex).toBeGreaterThan(1);
617
+ expect(buffer.lines.slice(headingIndex + 1).some(line => line.includes("Sibling body."))).toBe(true);
618
+ });
619
+
620
+ test("the heading's first character is a fixed toggle target", () => {
621
+ const { buffer } = tuiSelector(sectionMarkdown);
622
+ const headingRow = buffer.lines.findIndex(line => line.includes("Child"));
623
+ const firstCharacter = buffer.lines[headingRow].search(/\S/);
624
+
625
+ expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter + 1)).toBe(false);
626
+ expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
627
+ const headingRows = buffer._mdcuiHeadingRowIndex;
628
+ expect(headingRows.byRow.get(headingRow).id).toBe("child");
629
+ expect(headingRows.entries.some((heading) => heading.id === "grandchild")).toBe(false);
630
+ expect(buffer.lines.some(line => line.includes("Child body."))).toBe(false);
631
+ expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
632
+ expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
633
+ expect(headingRows.entries.some((heading) => heading.id === "grandchild")).toBe(true);
634
+ expect(buffer.lines.some(line => line.includes("Child body."))).toBe(true);
635
+ });
636
+
637
+ test("a normalized Chinese heading ID restores first-character toggling", () => {
638
+ const source = `# 中文 設定!
639
+
640
+ Chinese body.
641
+
642
+ # 結束
643
+
644
+ End body.
645
+ `;
646
+ const { $, buffer } = tuiSelector(source);
647
+ const headingRow = buffer.lines.findIndex(line =>
648
+ line.trim() === "中文 設定!"
649
+ );
650
+ const firstCharacter = buffer.lines[headingRow].search(/\S/);
651
+ const heading = buffer._mdcuiHeadingRowIndex.byRow.get(headingRow);
652
+
653
+ expect(heading).toMatchObject({
654
+ id: "中文-設定",
655
+ level: 1,
656
+ ordinal: 0,
657
+ });
658
+ const selection = $("#中文-設定");
659
+ const nested = $($({ id: "中文-設定", value: "decoy" }));
660
+ expect(selection.id).toBe("中文-設定");
661
+ expect(nested.id).toBe("中文-設定");
662
+ expect(nested.text()).toBe("中文 設定!");
663
+ expect(nested.data()).toBe(selection.data());
664
+
665
+ expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
666
+ expect(buffer.lines.some(line => line.includes("Chinese body."))).toBe(false);
667
+ expect(buffer.lines.some(line => line.trim() === "結束")).toBe(true);
668
+ expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
669
+ expect(buffer.lines.some(line => line.includes("Chinese body."))).toBe(true);
670
+ });
671
+
672
+ test("blockquote and list headings keep the TUI row index valid", () => {
673
+ const source = `# Top
674
+
675
+ > ## 引用標題
676
+ >
677
+ > Quoted body.
678
+
679
+ - ## 清單標題
680
+
681
+ List body.
682
+
683
+ # End
684
+ `;
685
+ const { $, buffer } = tuiSelector(source);
686
+ const quotedRow = $("#引用標題").line() - 1;
687
+ const listRow = $("#清單標題").line() - 1;
688
+ const topRow = $("#top").line() - 1;
689
+
690
+ expect(buffer._mdcuiHeadingRowIndex.valid).toBe(true);
691
+ expect(buffer._mdcuiHeadingRowIndex.entries).toHaveLength(4);
692
+ expect(buffer._mdcuiHeadingRowIndex.byRow.get(quotedRow)?.column)
693
+ .toBe(buffer.lines[quotedRow].indexOf("引"));
694
+ expect(buffer._mdcuiHeadingRowIndex.byRow.get(listRow)?.column)
695
+ .toBe(buffer.lines[listRow].indexOf("清"));
696
+
697
+ expect(toggleTuiHeadingAt(
698
+ buffer,
699
+ quotedRow,
700
+ buffer.lines[quotedRow].search(/\S/),
701
+ )).toBe(false);
702
+ expect(toggleTuiHeadingAt(
703
+ buffer,
704
+ quotedRow,
705
+ buffer.lines[quotedRow].indexOf("引"),
706
+ )).toBe(true);
707
+ expect(buffer.lines.some(line => line.includes("Quoted body."))).toBe(false);
708
+ expect(buffer.lines.some(line => line.includes("清單標題"))).toBe(true);
709
+ expect(toggleTuiHeadingAt(
710
+ buffer,
711
+ topRow,
712
+ buffer.lines[topRow].indexOf("Top"),
713
+ )).toBe(true);
714
+ });
715
+
716
+ test("nested heading sections stop at their Markdown container boundary", () => {
717
+ const scenarios = [
718
+ {
719
+ source: `> ## Quoted
720
+ >
721
+ > Inside quote.
722
+
723
+ Outside quote.
724
+
725
+ ### Outside lower
726
+
727
+ Outside heading body.
728
+ `,
729
+ id: "quoted",
730
+ inside: "Inside quote.",
731
+ outside: "Outside quote.",
732
+ },
733
+ {
734
+ source: `- Item
735
+
736
+ ## Listed
737
+
738
+ Inside list.
739
+
740
+ Outside list.
741
+
742
+ ### Outside lower
743
+
744
+ Outside heading body.
745
+ `,
746
+ id: "listed",
747
+ inside: "Inside list.",
748
+ outside: "Outside list.",
749
+ },
750
+ ];
751
+
752
+ for (const scenario of scenarios) {
753
+ const { $, buffer } = tuiSelector(scenario.source);
754
+ const heading = $(`#${scenario.id}`);
755
+
756
+ expect(heading.hide()).toBe(heading);
757
+ expect(buffer.lines.some(line => line.includes(scenario.inside)))
758
+ .toBe(false);
759
+ expect(buffer.lines.some(line => line.includes(scenario.outside)))
760
+ .toBe(true);
761
+ expect(buffer.lines.some(line => line.includes("Outside lower")))
762
+ .toBe(true);
763
+
764
+ heading.show();
765
+ expect(buffer.lines.some(line => line.includes(scenario.inside)))
766
+ .toBe(true);
767
+ }
768
+ });
769
+
770
+ test("blockquote visibility stops before a following top-level list", () => {
771
+ const source = `> ## Quoted
772
+ >
773
+ > Inside quote.
774
+
775
+ - Outside list item
776
+ `;
777
+ const { $, buffer } = tuiSelector(source);
778
+ const original = buffer.lines.slice();
779
+ const quoted = $("#quoted");
780
+
781
+ quoted.hide();
782
+ expect(buffer.lines.some(line => line.includes("Inside quote."))).toBe(false);
783
+ expect(buffer.lines.some(line => line.includes("Outside list item"))).toBe(true);
784
+
785
+ quoted.show();
786
+ expect(buffer.lines).toEqual(original);
787
+ });
788
+
789
+ test("double-blockquote visibility crosses compact quote marker rows only", () => {
790
+ const source = `> > ## Deep
791
+ > >
792
+ > > Deep body.
793
+ >
794
+ > Outer body.
795
+
796
+ Outside body.
797
+ `;
798
+ const { $, buffer } = tuiSelector(source);
799
+ const original = buffer.lines.slice();
800
+ const deep = $("#deep");
801
+
802
+ expect(buffer.lines.some(line => line === "││")).toBe(true);
803
+ deep.hide();
804
+ expect(buffer.lines.some(line => line.includes("Deep body."))).toBe(false);
805
+ expect(buffer.lines.some(line => line.includes("Outer body."))).toBe(true);
806
+ expect(buffer.lines.some(line => line.includes("Outside body."))).toBe(true);
807
+
808
+ deep.show();
809
+ expect(buffer.lines).toEqual(original);
810
+ });
811
+
812
+ test("a large document resolves the cursor row without rebuilding per heading", () => {
813
+ const headingCount = 2000;
814
+ const source = Array.from(
815
+ { length: headingCount },
816
+ (_, index) => `## Topic ${index}\n\nBody ${index}.`,
817
+ ).join("\n\n");
818
+ const { buffer } = tuiSelector(source);
819
+ const lastRow = buffer.lines.findLastIndex((line) =>
820
+ line.trim() === `Topic ${headingCount - 1}`
821
+ );
822
+ const firstCharacter = buffer.lines[lastRow].search(/\S/);
823
+ const headingRows = buffer._mdcuiHeadingRowIndex;
824
+
825
+ expect(toggleTuiHeadingAt(buffer, lastRow, firstCharacter)).toBe(true);
826
+ expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
827
+ expect(headingRows.valid).toBe(true);
828
+ expect(headingRows.entries).toHaveLength(headingCount);
829
+ expect(headingRows.byRow.get(lastRow)).toMatchObject({
830
+ id: `topic-${headingCount - 1}`,
831
+ ordinal: headingCount - 1,
832
+ level: 2,
833
+ });
834
+
835
+ expect(toggleTuiHeadingAt(buffer, lastRow, firstCharacter)).toBe(true);
836
+ expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
837
+ const bodyRow = buffer.lines.findIndex((line) =>
838
+ line.trim() === `Body ${headingCount - 1}.`
839
+ );
840
+ expect(toggleTuiHeadingAt(buffer, bodyRow, buffer.lines[bodyRow].search(/\S/))).toBe(false);
841
+ expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
842
+ });
843
+
844
+ test("text block row-count changes move cached heading rows in place", () => {
845
+ const source = `# Parent
846
+
847
+ \`\`\`text#editor
848
+ one
849
+ \`\`\`
850
+
851
+ ## Child
852
+
853
+ Child body.
854
+
855
+ # End
856
+ `;
857
+ const { $, buffer } = tuiSelector(source);
858
+ const parentRow = buffer.lines.findIndex((line) => line.trim() === "Parent");
859
+ const parentColumn = buffer.lines[parentRow].search(/\S/);
860
+ expect(toggleTuiHeadingAt(buffer, parentRow, parentColumn)).toBe(true);
861
+ expect(toggleTuiHeadingAt(buffer, parentRow, parentColumn)).toBe(true);
862
+ const headingRows = buffer._mdcuiHeadingRowIndex;
863
+ const oldChildRow = headingRows.entries.find((heading) => heading.id === "child").row;
864
+
865
+ $("text#editor").val("one\ntwo\nthree\nfour");
866
+
867
+ const child = headingRows.entries.find((heading) => heading.id === "child");
868
+ expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
869
+ expect(child.row).toBeGreaterThan(oldChildRow);
870
+ expect(headingRows.byRow.has(oldChildRow)).toBe(false);
871
+ expect(headingRows.byRow.get(child.row)?.id).toBe("child");
872
+ expect(toggleTuiHeadingAt(
873
+ buffer,
874
+ child.row,
875
+ buffer.lines[child.row].search(/\S/),
876
+ )).toBe(true);
877
+ expect(buffer.lines.some((line) => line.includes("Child body."))).toBe(false);
878
+ });
879
+
880
+ test("a direct rerender invalidates row metadata and mismatches fail closed", () => {
881
+ const source = `# First
882
+
883
+ This paragraph is deliberately long enough to wrap at a narrow render width.
884
+
885
+ ## Child
886
+
887
+ Child body.
888
+ `;
889
+ const { buffer } = tuiSelector(source);
890
+ const firstRow = buffer.lines.findIndex((line) => line.trim() === "First");
891
+ const firstColumn = buffer.lines[firstRow].search(/\S/);
892
+ expect(toggleTuiHeadingAt(buffer, firstRow, firstColumn)).toBe(true);
893
+ expect(toggleTuiHeadingAt(buffer, firstRow, firstColumn)).toBe(true);
894
+ const wideHeadingRows = buffer._mdcuiHeadingRowIndex;
895
+
896
+ const narrowAnsi = String(Bun.markdown.ansi(
897
+ source,
898
+ { hyperlinks: true, columns: 20 },
899
+ ));
900
+ buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
901
+ buffer._mdcuiAnsiText = narrowAnsi;
902
+ const childRow = buffer.lines.findIndex((line) => line.trim() === "Child");
903
+ expect(toggleTuiHeadingAt(
904
+ buffer,
905
+ childRow,
906
+ buffer.lines[childRow].search(/\S/),
907
+ )).toBe(true);
908
+ expect(buffer._mdcuiHeadingRowIndex).not.toBe(wideHeadingRows);
909
+ expect(buffer._mdcuiHeadingRowIndex.byRow.get(childRow)?.id).toBe("child");
910
+
911
+ const mismatched = tuiSelector("# First\n\n# Second\n").buffer;
912
+ const oneHeadingAnsi = String(Bun.markdown.ansi(
913
+ "# First\n",
914
+ { hyperlinks: true, columns: 80 },
915
+ ));
916
+ mismatched.lines = Bun.stripANSI(oneHeadingAnsi).split("\n");
917
+ mismatched._mdcuiAnsiText = oneHeadingAnsi;
918
+ const row = mismatched.lines.findIndex((line) => line.trim() === "First");
919
+ const column = mismatched.lines[row].search(/\S/);
920
+ expect(toggleTuiHeadingAt(mismatched, row, column)).toBe(false);
921
+ const failedIndex = mismatched._mdcuiHeadingRowIndex;
922
+ expect(failedIndex.valid).toBe(false);
923
+ expect(toggleTuiHeadingAt(mismatched, row, column)).toBe(false);
924
+ expect(mismatched._mdcuiHeadingRowIndex).toBe(failedIndex);
925
+ });
926
+ });
927
+
928
+ describe("TUI id-centered user data", () => {
929
+ test("data returns the same object stored under the selector id", () => {
930
+ const { $, buffer } = tuiSelector("## Features\n\nBody.\n");
931
+ const features = $("#features");
932
+ const data = features.data();
933
+
934
+ data.count = 1;
935
+ expect(features.data()).toBe(data);
936
+ expect(buffer._mdcuiIdStore.get("features").data).toBe(data);
937
+ expect(features.data("count")).toBe(1);
938
+ });
939
+
940
+ test("data setters and removeData preserve other id state", () => {
941
+ const { $, buffer } = tuiSelector("## Features\n\nBody.\n");
942
+ const features = $("#features");
943
+
944
+ features.data("model", { ready: true }).data({ count: 2, label: "x" });
945
+ features.hide();
946
+ features.removeData("count label");
947
+ expect(features.data("model")).toEqual({ ready: true });
948
+ features.removeData();
949
+ expect(buffer._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
950
+ expect(buffer._mdcuiIdStore.get("features").data).toBeUndefined();
951
+ });
952
+
953
+ test("legal object ids discard the object and retain identity through arbitrary nesting", () => {
954
+ const { $, buffer } = tuiSelector("## Features\n\n- [x] Search\n");
955
+ const features = $("#features");
956
+ const objectTarget = {
957
+ id: "features",
958
+ innerHTML: "DECOY HTML",
959
+ textContent: "DECOY TEXT",
960
+ value: "DECOY VALUE",
961
+ };
962
+ const direct = $(objectTarget);
963
+ expect(direct.text()).toBe("Features");
964
+ expect(direct.val()).toEqual(["Search"]);
965
+ objectTarget.id = "missing";
966
+ let nested = direct;
967
+ for (let depth = 0; depth < 8; depth++) nested = $(nested);
968
+
969
+ expect(features.id).toBe("features");
970
+ expect(nested.id).toBe("features");
971
+ expect(nested.html()).toBe("Features");
972
+ expect(nested.text()).toBe("Features");
973
+ expect(nested.val()).toEqual(["Search"]);
974
+ expect(nested.text("Ignored")).toBe(nested);
975
+ expect(nested.val("Ignored")).toBe(nested);
976
+ expect(objectTarget).toEqual({
977
+ id: "missing",
978
+ innerHTML: "DECOY HTML",
979
+ textContent: "DECOY TEXT",
980
+ value: "DECOY VALUE",
981
+ });
982
+ features.data("count", 1);
983
+ expect($({ id: "features" }).data("count")).toBe(1);
984
+ expect(nested.data()).toBe(features.data());
985
+ expect(nested.push("Nested addition")).toBe(2);
986
+ expect(buffer._mdcuiMutationMacros.at(-1).selector).toBe("#features");
987
+ nested.hide();
988
+ expect(buffer._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
989
+ });
990
+
991
+ test("Unicode and numeric heading IDs remain canonical through object wrapping", () => {
992
+ const { $ } = tuiSelector("# 中文\n\nBody.\n\n# 2026 中文\n\nLater.\n");
993
+
994
+ for (const id of ["中文", "2026"]) {
995
+ const direct = $(`#${id}`);
996
+ let nested = $({ id, value: "decoy" });
997
+ for (let depth = 0; depth < 4; depth++) nested = $(nested);
998
+ expect(nested.id).toBe(id);
999
+ expect(nested.text()).toBe(direct.text());
1000
+ expect(nested.data()).toBe(direct.data());
1001
+ }
1002
+ });
1003
+ });
1004
+
1005
+ describe("TUI source-dependent state reset", () => {
1006
+ test("checkbox count mismatches provide a visible rerender message", () => {
1007
+ expect(tuiCheckboxRerenderMismatchMessage({
1008
+ name: "tasks.md",
1009
+ _mdcuiRerenderMismatch: {
1010
+ checkboxes: { before: 3, after: 2 },
1011
+ },
1012
+ })).toBe(
1013
+ "Checkbox state restore skipped in tasks.md: count changed from 3 to 2",
1014
+ );
1015
+ expect(tuiCheckboxRerenderMismatchMessage({
1016
+ _mdcuiRerenderMismatch: {
1017
+ fenceBlocks: { before: 1, after: 2 },
1018
+ },
1019
+ })).toBe("");
1020
+ });
1021
+
1022
+ test("reopen cache clearing drops old render state while preserving user data", () => {
1023
+ const oldSource = `# Topic
1024
+
1025
+ OLD BODY
1026
+
1027
+ - [ ] Original
1028
+
1029
+ # End
1030
+ `;
1031
+ const { $, buffer } = tuiSelector(oldSource);
1032
+ const topic = $("#topic");
1033
+ const data = topic.data();
1034
+ data.count = 1;
1035
+ topic.push("Old runtime item");
1036
+ topic.hide();
1037
+ const record = buffer._mdcuiIdStore.get("topic");
1038
+ record.customState = { keep: true };
1039
+ buffer._mdcuiRerenderMismatch = { checkboxes: { before: 2, after: 1 } };
1040
+ buffer._mdcuiFenceBlockIndex = [{ stale: true }];
1041
+ buffer._mdcuiControlBlockIndex = [{ stale: true }];
1042
+
1043
+ const newSource = `# Topic
1044
+
1045
+ NEW BODY
1046
+
1047
+ - [ ] Fresh
1048
+
1049
+ # End
1050
+ `;
1051
+ const newAnsi = String(Bun.markdown.ansi(
1052
+ newSource,
1053
+ { hyperlinks: true, columns: 80 },
1054
+ ));
1055
+ buffer.lines = Bun.stripANSI(newAnsi).split("\n");
1056
+ buffer._mdcuiTuiSourceText = newSource;
1057
+ buffer._mdcuiAnsiText = newAnsi;
1058
+ clearTuiSourceDependentState(buffer);
1059
+
1060
+ expect(buffer._mdcuiIdStore.get("topic")).toBe(record);
1061
+ expect(topic.data()).toBe(data);
1062
+ expect(topic.data("count")).toBe(1);
1063
+ expect(record.customState).toEqual({ keep: true });
1064
+ expect(record.headingVisibility).toBeUndefined();
1065
+ expect(buffer._mdcuiMutationMacros).toBeUndefined();
1066
+ expect(buffer._mdcuiReplayingMutations).toBe(false);
1067
+ expect(buffer._mdcuiHeadingTaskListAnchors).toBeNull();
1068
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1069
+ expect(buffer._mdcuiFenceBlockIndex).toBeNull();
1070
+ expect(buffer._mdcuiControlBlockIndex).toBeNull();
1071
+ expect(buffer._mdcuiSourceHeadingIndex).toBeNull();
1072
+ expect(buffer._mdcuiHeadingRowIndex).toBeNull();
1073
+
1074
+ topic.show();
1075
+ expect(buffer.lines.some(line => line.includes("OLD BODY"))).toBe(false);
1076
+ expect(buffer.lines.some(line => line.includes("Old runtime item"))).toBe(false);
1077
+ expect(buffer.lines.some(line => line.includes("Original"))).toBe(false);
1078
+ expect(buffer.lines.filter(line => line.includes("NEW BODY"))).toHaveLength(1);
1079
+ expect(topic.slice()).toEqual([{ value: "Fresh", checked: false }]);
1080
+ });
1081
+
1082
+ test("cache clearing prunes id records that contain only visibility state", () => {
1083
+ const buffer = {
1084
+ _mdcuiIdStore: new Map([
1085
+ ["only-visibility", {
1086
+ headingVisibility: {
1087
+ hidden: true,
1088
+ ordinal: 0,
1089
+ headingCount: 0,
1090
+ segment: { lines: [], images: [] },
1091
+ },
1092
+ }],
1093
+ ]),
1094
+ };
1095
+
1096
+ clearTuiSourceDependentState(buffer);
1097
+
1098
+ expect(buffer._mdcuiIdStore.has("only-visibility")).toBe(false);
1099
+ });
1100
+ });
1101
+
1102
+ describe("TUI object target wrapper", () => {
1103
+ test("text gets and sets textContent for synthetic event targets", () => {
1104
+ const { $ } = tuiSelector("Body.\n");
1105
+ const target = { textContent: "Save" };
1106
+ const wrapped = $(target);
1107
+
1108
+ expect(wrapped.text()).toBe("Save");
1109
+ expect(wrapped.text("Saved")).toBe(wrapped);
1110
+ expect(target.textContent).toBe("Saved");
1111
+ });
1112
+
1113
+ test("val uses value when present and otherwise falls back to textContent", () => {
1114
+ const { $ } = tuiSelector("Body.\n");
1115
+ const input = { value: "old", textContent: "label" };
1116
+ const link = { textContent: "Open" };
1117
+
1118
+ expect($(input).val()).toBe("old");
1119
+ $(input).val("new");
1120
+ expect(input.value).toBe("new");
1121
+ expect(input.textContent).toBe("label");
1122
+ expect($(link).val()).toBe("Open");
1123
+ $(link).val("Close");
1124
+ expect(link.textContent).toBe("Close");
1125
+ });
1126
+
1127
+ test("html reads generic innerHTML and val stringifies object values", () => {
1128
+ const { $ } = tuiSelector("Body.\n");
1129
+
1130
+ expect($({ innerHTML: "<b>Body</b>" }).html()).toBe("<b>Body</b>");
1131
+ expect($({ value: 123 }).val()).toBe("123");
1132
+ });
1133
+
1134
+ test("invalid object ids stay generic while missing legal ids do not", () => {
1135
+ const { $ } = tuiSelector("## Features\n\nBody.\n");
1136
+ const invalidTargets = [
1137
+ { id: "-invalid", value: "leading punctuation" },
1138
+ { id: "invalid id", value: "spaced" },
1139
+ ];
1140
+ const missing = { id: "missing", value: "decoy", textContent: "decoy" };
1141
+
1142
+ for (const target of invalidTargets) {
1143
+ const wrapped = $(target);
1144
+ expect(wrapped.id).toBe("");
1145
+ expect(wrapped.val()).toBe(target.value);
1146
+ wrapped.val("updated");
1147
+ expect(target.value).toBe("updated");
1148
+ }
1149
+ expect($(missing).val()).toBe("");
1150
+ $(missing).val("ignored");
1151
+ expect(missing.value).toBe("decoy");
1152
+ expect(missing.textContent).toBe("decoy");
1153
+ });
1154
+ });
1155
+
1156
+ describe("TUI heading text getter", () => {
1157
+ test("returns the rendered heading text without enabling a string-selector setter", () => {
1158
+ const { $, buffer } = tuiSelector("## Hello *world* & `<tag>`\n\nBody.\n");
1159
+ const heading = $("#hello-world-tag");
1160
+
1161
+ expect(heading.text()).toBe("Hello world & <tag>");
1162
+ expect(heading.text("Replacement")).toBe(heading);
1163
+ expect(heading.text()).toBe("Hello world & <tag>");
1164
+ expect(buffer.lines.some((line) => line.includes("Replacement"))).toBe(false);
1165
+ });
1166
+
1167
+ test("remains available while the heading section is hidden", () => {
1168
+ const { $ } = tuiSelector("## Hidden title\n\nBody.\n");
1169
+ const heading = $("#hidden-title");
1170
+
1171
+ heading.hide();
1172
+ expect(heading.text()).toBe("Hidden title");
1173
+ });
1174
+ });
1175
+
1176
+ describe("TUI heading image selection", () => {
1177
+ test("img(index).src reads OSC 8 image destinations before the next heading", () => {
1178
+ const { $ } = tuiSelector(`# Gallery
1179
+
1180
+ ![First](one.png)
1181
+
1182
+ ![Second](two.png)
1183
+
1184
+ ## Child
1185
+
1186
+ ![Child](child.png)
1187
+ `);
1188
+
1189
+ expect($("#gallery").img(0).src).toBe("one.png");
1190
+ expect($("#gallery").img(1).src).toBe("two.png");
1191
+ expect($("#gallery").img(2).src).toBe("");
1192
+ expect($("#child").img(0).src).toBe("child.png");
1193
+ expect($("#gallery").img(-1).src).toBe("");
1194
+ });
1195
+ });
1196
+
1197
+ describe("TUI heading-associated table cells", () => {
1198
+ const tableMarkdown = `## Table With Id
1199
+
1200
+ | Name | Status |
1201
+ | --- | --- |
1202
+ | very long content here | ready |
1203
+ | other | done |
1204
+ `;
1205
+
1206
+ test("cell text uses zero-based coordinates and joins wrapped visual lines", () => {
1207
+ const { $ } = tuiTableSelector(tableMarkdown, 24);
1208
+ const table = $("#table-with-id");
1209
+
1210
+ expect(table.cell(0, 0).text()).toBe("Name");
1211
+ expect(table.cell(1, 0).text()).toBe("very longcontenthere");
1212
+ expect(table.cell(2, 1).text()).toBe("done");
1213
+ expect(table.cell(99, 0).text()).toBe("");
1214
+ expect(table.cell(-1, 0).text("ignored")).toBeDefined();
1215
+ });
1216
+
1217
+ test("cell properties and physical neighbors stay inside the table", () => {
1218
+ const { $ } = tuiTableSelector(tableMarkdown, 24);
1219
+ const table = $("#table-with-id");
1220
+ const cell = table.cell(1, 0);
1221
+
1222
+ expect(cell.row).toBe(1);
1223
+ expect(cell.col).toBe(0);
1224
+ expect(cell.right().text()).toBe("ready");
1225
+ expect(cell.rt().text()).toBe("ready");
1226
+ expect(cell.up().text()).toBe("Name");
1227
+ expect(cell.down().text()).toBe("other");
1228
+ expect(cell.dn().text()).toBe("other");
1229
+ expect(cell.right().lt().text()).toBe("very longcontenthere");
1230
+ expect(cell.right().down().text()).toBe("done");
1231
+ expect(table.cell(0, 0).left()).toBeNull();
1232
+ expect(table.cell(0, 0).lt()).toBeNull();
1233
+ expect(table.cell(0, 0).up()).toBeNull();
1234
+ expect(table.cell(2, 1).right()).toBeNull();
1235
+ expect(table.cell(2, 1).rt()).toBeNull();
1236
+ expect(table.cell(2, 1).down()).toBeNull();
1237
+ expect(table.cell(2, 1).dn()).toBeNull();
1238
+ expect(table.cell(99, 0).left()).toBeNull();
1239
+ });
1240
+
1241
+ test("cell val returns the first checkbox state or falls back to text", () => {
1242
+ const markdown = `## Checks
1243
+
1244
+ | Name | State |
1245
+ | --- | --- |
1246
+ | plain | [x] checked |
1247
+ | empty | [ ] pending |
1248
+ `;
1249
+ const { $, buffer } = tuiTableSelector(markdown, 30);
1250
+ const table = $("#checks");
1251
+
1252
+ expect(table.cell(0, 0).val()).toBe("Name");
1253
+ expect(table.cell(1, 0).val()).toBe("plain");
1254
+ expect(table.cell(1, 1).val()).toBe(true);
1255
+ expect(table.cell(2, 1).val()).toBe(false);
1256
+ const uncheckedCell = table.cell(2, 1);
1257
+ expect(uncheckedCell.val(true)).toBe(uncheckedCell);
1258
+ expect(uncheckedCell.val()).toBe(true);
1259
+ expect(table.cell(1, 1).val(false).val()).toBe(false);
1260
+ expect(table.cell(1, 0).val(true).text()).toBe("plain");
1261
+ expect(buffer._mdcuiAnsiText).toContain("\x1b[32m☒");
1262
+ expect(table.cell(99, 0).val()).toBe("");
1263
+ });
1264
+
1265
+ test("cell text replacement stays inside its rectangle and updates ANSI", () => {
1266
+ const { $, buffer } = tuiTableSelector(tableMarkdown, 24);
1267
+ const cell = $("#table-with-id").cell(2, 1);
1268
+ const widths = buffer.lines.map(line => Bun.stringWidth(line));
1269
+ const result = cell.text("更新✅abcdef");
1270
+
1271
+ expect(result).toBe(cell);
1272
+ expect(cell.text()).toBe("更新✅");
1273
+ expect(buffer.lines.map(line => Bun.stringWidth(line))).toEqual(widths);
1274
+ expect(Bun.stripANSI(buffer._mdcuiAnsiText)).toBe(buffer.lines.join("\n"));
1275
+ expect(buffer.undoCount).toBe(1);
1276
+ expect(buffer._mdcuiMutationMacros).toBeUndefined();
1277
+ });
1278
+
1279
+ test("cell text replacement preserves every ANSI and OSC 8 control", () => {
1280
+ const linkedMarkdown = `## Linked Table
1281
+
1282
+ | Name | Action |
1283
+ | --- | --- |
1284
+ | item | [open](javascript:run()) |
1285
+ `;
1286
+ const { $, buffer } = tuiTableSelector(linkedMarkdown, 40);
1287
+ const controls = value => String(value).match(
1288
+ /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\))/gu,
1289
+ ) ?? [];
1290
+ const before = controls(buffer._mdcuiAnsiText);
1291
+
1292
+ $("#linked-table").cell(1, 1).text("執行");
1293
+
1294
+ expect($("#linked-table").cell(1, 1).text()).toBe("執行");
1295
+ expect(controls(buffer._mdcuiAnsiText)).toEqual(before);
1296
+ expect(buffer._mdcuiAnsiText).toContain("javascript:run()");
1297
+ });
1298
+
1299
+ test("a table link parent exposes the complete cell selection", () => {
1300
+ const linkedMarkdown = `## Linked Table
1301
+
1302
+ | Name | Action |
1303
+ | --- | --- |
1304
+ | item | [open](javascript:run()) |
1305
+ | next | later |
1306
+ `;
1307
+ const { $, buffer } = tuiTableSelector(linkedMarkdown, 40);
1308
+ const y = buffer.lines.findIndex(line => line.includes("open"));
1309
+ const x = buffer.lines[y].indexOf("open");
1310
+ const parentCell = tuiTableCellAtPosition(buffer, y, x);
1311
+ const linkTarget = { parent: () => parentCell };
1312
+ const parent = $(linkTarget).parent();
1313
+
1314
+ expect(parent.row).toBe(1);
1315
+ expect(parent.col).toBe(1);
1316
+ expect(parent.text()).toBe("open");
1317
+ expect(parent.lt().text()).toBe("item");
1318
+ expect(parent.dn().text()).toBe("later");
1319
+ expect(parent.text("執行")).toBe(parent);
1320
+ expect(parent.text()).toBe("執行");
1321
+ });
1322
+
1323
+ test("cell snapshots restore API replacements after a width rerender", () => {
1324
+ const { $, buffer } = tuiTableSelector(tableMarkdown, 24);
1325
+ $("#table-with-id").cell(1, 0).text("replacement value");
1326
+ const snapshot = captureTuiRerenderState(buffer);
1327
+
1328
+ const rerendered = tuiTableSelector(tableMarkdown, 32).buffer;
1329
+ buffer.lines = rerendered.lines;
1330
+ buffer._mdcuiAnsiText = rerendered._mdcuiAnsiText;
1331
+ buffer._ansiStyleLines = rerendered._ansiStyleLines;
1332
+ buffer._mdcuiHeadingRowIndex = null;
1333
+ restoreTuiRerenderState(buffer, snapshot);
1334
+
1335
+ expect($("#table-with-id").cell(1, 0).text()).toBe("replacement value");
1336
+ expect(buffer.undoCount).toBe(1);
1337
+ });
1338
+
1339
+ test("cell snapshots restore direct edits with ANSI after a width rerender", () => {
1340
+ const { $, buffer } = tuiTableSelector(tableMarkdown, 24);
1341
+ const y = buffer.lines.findIndex(line => line.includes("very long"));
1342
+ const x = buffer.lines[y].indexOf("very");
1343
+ markTuiTableCellDirtyAtPosition(buffer, y, x);
1344
+ buffer.lines[y] =
1345
+ buffer.lines[y].slice(0, x) + "USER" + buffer.lines[y].slice(x + 4);
1346
+ const ansiLines = buffer._mdcuiAnsiText.split("\n");
1347
+ ansiLines[y] = colorAnsiPlainRange(
1348
+ ansiLines[y],
1349
+ x,
1350
+ x + 4,
1351
+ "USER",
1352
+ "\x1b[35m",
1353
+ );
1354
+ buffer._mdcuiAnsiText = ansiLines.join("\n");
1355
+ for (let index = x; index < x + 4; index++)
1356
+ buffer._ansiStyleLines[y][index] = { fg: "magenta" };
1357
+ const snapshot = captureTuiRerenderState(buffer);
1358
+
1359
+ const rerendered = tuiTableSelector(tableMarkdown, 32).buffer;
1360
+ buffer.lines = rerendered.lines;
1361
+ buffer._mdcuiAnsiText = rerendered._mdcuiAnsiText;
1362
+ buffer._ansiStyleLines = rerendered._ansiStyleLines;
1363
+ buffer._mdcuiHeadingRowIndex = null;
1364
+ restoreTuiRerenderState(buffer, snapshot);
1365
+
1366
+ expect($("#table-with-id").cell(1, 0).text())
1367
+ .toBe("USER longcontenthere");
1368
+ expect(buffer._mdcuiAnsiText).toContain("\x1b[35mUSER");
1369
+ const restoredY = buffer.lines.findIndex(line => line.includes("USER"));
1370
+ const restoredX = buffer.lines[restoredY].indexOf("USER");
1371
+ expect(buffer._ansiStyleLines[restoredY][restoredX]?.fg).toBe("magenta");
1372
+ expect(buffer.undoCount).toBeUndefined();
1373
+ });
1374
+ });
1375
+
1376
+ describe("TUI resize state restoration", () => {
1377
+ test("restores every checkbox glyph by full-text order without block filtering", () => {
1378
+ const buffer = {
1379
+ name: "glyphs.md",
1380
+ lines: [
1381
+ "Legend: ☒ done and ☐ pending",
1382
+ "│ ☒ table value │",
1383
+ "```text#sample",
1384
+ "☐ fenced value",
1385
+ "```",
1386
+ ],
1387
+ _mdcuiTuiSourceText: "",
1388
+ _mdcuiAnsiText: "",
1389
+ cursor: { x: 0, y: 0 },
1390
+ ensureCursor() {},
1391
+ invalidateHighlightFrom() {},
1392
+ };
1393
+ const snapshot = captureTuiRerenderState(buffer);
1394
+ buffer.lines = [
1395
+ "Legend: ☐ done and ☒ pending",
1396
+ "│ ☐ table value │",
1397
+ "```text#sample",
1398
+ "☒ fenced value",
1399
+ "```",
1400
+ ];
1401
+ restoreTuiRerenderState(buffer, snapshot);
1402
+
1403
+ expect(buffer.lines).toEqual([
1404
+ "Legend: ☒ done and ☐ pending",
1405
+ "│ ☒ table value │",
1406
+ "```text#sample",
1407
+ "☐ fenced value",
1408
+ "```",
1409
+ ]);
1410
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1411
+ });
1412
+
1413
+ test("restores exact ANSI styles for checkbox glyphs by visible column", () => {
1414
+ const originalAnsi = [
1415
+ "中文 \x1b[1;38;2;12;34;56;48;5;20m☒\x1b[0m suffix",
1416
+ "\x1b[35m😀 ☐ tail\x1b[0m",
1417
+ ].join("\n");
1418
+ const buffer = {
1419
+ name: "styled.md",
1420
+ lines: Bun.stripANSI(originalAnsi).split("\n"),
1421
+ _mdcuiTuiSourceText: "",
1422
+ _mdcuiAnsiText: originalAnsi,
1423
+ _ansiStyleLines: [
1424
+ Array.from({ length: 20 }, () => null),
1425
+ Array.from({ length: 20 }, () => null),
1426
+ ],
1427
+ cursor: { x: 0, y: 0 },
1428
+ ensureCursor() {},
1429
+ invalidateHighlightFrom() {},
1430
+ };
1431
+ const firstX = buffer.lines[0].indexOf("☒");
1432
+ const secondX = buffer.lines[1].indexOf("☐");
1433
+ buffer._ansiStyleLines[0][firstX] = {
1434
+ bold: true,
1435
+ fg: "#0c2238",
1436
+ bg: 20,
1437
+ };
1438
+ buffer._ansiStyleLines[1][secondX] = { fg: "magenta" };
1439
+ const snapshot = captureTuiRerenderState(buffer);
1440
+
1441
+ const rerenderedAnsi = [
1442
+ "中文 \x1b[31m☐\x1b[0m suffix",
1443
+ "\x1b[36m😀 ☒ tail\x1b[0m",
1444
+ ].join("\n");
1445
+ buffer.lines = Bun.stripANSI(rerenderedAnsi).split("\n");
1446
+ buffer._mdcuiAnsiText = rerenderedAnsi;
1447
+ buffer._ansiStyleLines[0][firstX] = { fg: "red" };
1448
+ buffer._ansiStyleLines[1][secondX] = { fg: "cyan" };
1449
+ restoreTuiRerenderState(buffer, snapshot);
1450
+
1451
+ expect(buffer.lines).toEqual(Bun.stripANSI(originalAnsi).split("\n"));
1452
+ expect(buffer._ansiStyleLines[0][firstX]).toEqual({
1453
+ bold: true,
1454
+ fg: "#0c2238",
1455
+ bg: 20,
1456
+ });
1457
+ expect(buffer._ansiStyleLines[1][secondX]).toEqual({ fg: "magenta" });
1458
+ expect(
1459
+ Bun.sliceAnsi(buffer._mdcuiAnsiText.split("\n")[0], 5, 6),
1460
+ ).toBe(
1461
+ Bun.sliceAnsi(originalAnsi.split("\n")[0], 5, 6),
1462
+ );
1463
+ expect(
1464
+ Bun.sliceAnsi(buffer._mdcuiAnsiText.split("\n")[1], 3, 4),
1465
+ ).toBe(
1466
+ Bun.sliceAnsi(originalAnsi.split("\n")[1], 3, 4),
1467
+ );
1468
+ });
1469
+
1470
+ test("preserves direct checkbox state inside a blockquote", () => {
1471
+ const source = `> ## Quoted
1472
+ >
1473
+ > - [ ] Inside quote
1474
+
1475
+ - [ ] Outside quote
1476
+ `;
1477
+ const { $, buffer } = tuiSelector(source);
1478
+ const inside = buffer.lines.findIndex(line => line.includes("Inside quote"));
1479
+ buffer.lines[inside] = buffer.lines[inside].replace("☐", "☒");
1480
+
1481
+ const snapshot = captureTuiRerenderState(buffer);
1482
+ const narrowAnsi = String(Bun.markdown.ansi(
1483
+ source,
1484
+ { hyperlinks: true, columns: 24 },
1485
+ ));
1486
+ buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
1487
+ buffer._mdcuiAnsiText = narrowAnsi;
1488
+ buffer._ansiStyleLines = null;
1489
+ buffer._mdcuiHeadingTaskListAnchors = null;
1490
+ restoreTuiRerenderState(buffer, snapshot);
1491
+
1492
+ expect($("#quoted").val()).toEqual(["Inside quote"]);
1493
+ expect(buffer.lines.some(line =>
1494
+ line.includes("Outside quote") && line.includes("☐")
1495
+ )).toBe(true);
1496
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1497
+ });
1498
+
1499
+ test("replays a mutation recorded through arbitrarily nested id selections", () => {
1500
+ const source = "## Features\n\n- [ ] Original\n";
1501
+ const { $, buffer } = tuiSelector(source);
1502
+ const objectTarget = { id: "features", value: "DECOY" };
1503
+ let nested = $(objectTarget);
1504
+ for (let depth = 0; depth < 8; depth++) nested = $(nested);
1505
+
1506
+ expect(nested.push({ value: "Added through nesting", checked: true })).toBe(2);
1507
+ expect(buffer._mdcuiMutationMacros).toHaveLength(1);
1508
+ expect(buffer._mdcuiMutationMacros[0].selector).toBe("#features");
1509
+
1510
+ const snapshot = captureTuiRerenderState(buffer);
1511
+ const narrowAnsi = String(Bun.markdown.ansi(
1512
+ source,
1513
+ { hyperlinks: true, columns: 24 },
1514
+ ));
1515
+ buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
1516
+ buffer._mdcuiAnsiText = narrowAnsi;
1517
+ buffer._ansiStyleLines = null;
1518
+ buffer._mdcuiHeadingTaskListAnchors = null;
1519
+ restoreTuiRerenderState(buffer, snapshot);
1520
+
1521
+ const items = nested.slice();
1522
+ expect(items).toEqual([
1523
+ { value: "Original", checked: false },
1524
+ { value: "Added through nesting", checked: true },
1525
+ ]);
1526
+ expect(items.filter(item => item.value === "Added through nesting"))
1527
+ .toHaveLength(1);
1528
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1529
+ expect(objectTarget.value).toBe("DECOY");
1530
+ });
1531
+
1532
+ test("replays Infinity, NaN, BigInt, and cyclic list mutation arguments", () => {
1533
+ const source = `## Features
1534
+
1535
+ - [ ] Original A
1536
+ - [x] Original B
1537
+ `;
1538
+ const { $, buffer } = tuiSelector(source);
1539
+ const features = $("#features");
1540
+ const cyclic = { value: "Cyclic item", checked: false };
1541
+ const bigintItem = { value: 9007199254740993n, checked: true };
1542
+ cyclic.self = cyclic;
1543
+
1544
+ expect(features.splice(0, Infinity)).toEqual(["Original A", "Original B"]);
1545
+ expect(features.push(bigintItem)).toBe(1);
1546
+ expect(features.splice(NaN, 0, cyclic)).toEqual([]);
1547
+ expect(features.slice()).toEqual([
1548
+ { value: "Cyclic item", checked: false },
1549
+ { value: "9007199254740993", checked: true },
1550
+ ]);
1551
+ cyclic.value = "Mutated cyclic item";
1552
+ cyclic.checked = true;
1553
+ bigintItem.value = 0n;
1554
+ bigintItem.checked = false;
1555
+
1556
+ const snapshot = captureTuiRerenderState(buffer);
1557
+ const narrowAnsi = String(Bun.markdown.ansi(
1558
+ source,
1559
+ { hyperlinks: true, columns: 24 },
1560
+ ));
1561
+ buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
1562
+ buffer._mdcuiAnsiText = narrowAnsi;
1563
+ buffer._ansiStyleLines = null;
1564
+ buffer._mdcuiHeadingTaskListAnchors = null;
1565
+ restoreTuiRerenderState(buffer, snapshot);
1566
+
1567
+ expect(features.slice()).toEqual([
1568
+ { value: "Cyclic item", checked: false },
1569
+ { value: "9007199254740993", checked: true },
1570
+ ]);
1571
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1572
+ });
1573
+
1574
+ test("preserves a deep visibility change made through hidden ancestors", () => {
1575
+ const source = `# First
1576
+
1577
+ First body.
1578
+
1579
+ ## Child
1580
+
1581
+ Child body.
1582
+
1583
+ ### Grandchild
1584
+
1585
+ Grandchild body.
1586
+
1587
+ # End
1588
+ `;
1589
+ const { $, buffer } = tuiSelector(source);
1590
+ $("#grandchild").hide();
1591
+ $("#child").hide();
1592
+ $("#first").hide();
1593
+ $("#grandchild").show();
1594
+
1595
+ const snapshot = captureTuiRerenderState(buffer);
1596
+ const narrowAnsi = String(Bun.markdown.ansi(
1597
+ source,
1598
+ { hyperlinks: true, columns: 24 },
1599
+ ));
1600
+ buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
1601
+ buffer._mdcuiAnsiText = narrowAnsi;
1602
+ buffer._ansiStyleLines = null;
1603
+ buffer._mdcuiHeadingTaskListAnchors = null;
1604
+ restoreTuiRerenderState(buffer, snapshot);
1605
+
1606
+ expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
1607
+ expect(buffer._mdcuiIdStore.get("child").headingVisibility.hidden).toBe(true);
1608
+ expect(buffer._mdcuiIdStore.get("grandchild")?.headingVisibility).toBeUndefined();
1609
+ $("#first").show();
1610
+ $("#child").show();
1611
+ expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(true);
1612
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1613
+ });
1614
+
1615
+ test("replays list macros, restores checkbox order, and re-hides deepest headings first", () => {
1616
+ const source = `# Parent
1617
+
1618
+ - [ ] A checkbox label long enough to wrap after resizing
1619
+
1620
+ ## Child
1621
+
1622
+ - [ ] Child checkbox
1623
+
1624
+ ### Deep
1625
+
1626
+ - [ ] Deep checkbox
1627
+
1628
+ # End
1629
+ `;
1630
+ const { $, buffer } = tuiSelector(source);
1631
+ $("#parent").push({ value: "Added by macro", checked: false });
1632
+ for (const needle of ["Child checkbox", "Deep checkbox"]) {
1633
+ const y = buffer.lines.findIndex((line) => line.includes(needle));
1634
+ buffer.lines[y] = buffer.lines[y].replace("☐", "☒");
1635
+ }
1636
+ $("#deep").hide();
1637
+ $("#parent").hide();
1638
+
1639
+ const snapshot = captureTuiRerenderState(buffer);
1640
+ const narrowAnsi = String(Bun.markdown.ansi(source, { hyperlinks: true, columns: 24 }));
1641
+ buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
1642
+ buffer._mdcuiAnsiText = narrowAnsi;
1643
+ buffer._ansiStyleLines = null;
1644
+ buffer._mdcuiHeadingTaskListAnchors = null;
1645
+ restoreTuiRerenderState(buffer, snapshot);
1646
+
1647
+ expect(buffer._mdcuiIdStore.get("parent").headingVisibility.hidden).toBe(true);
1648
+ expect(buffer._mdcuiIdStore.get("deep").headingVisibility.hidden).toBe(true);
1649
+ $("#parent").show();
1650
+ expect(buffer.lines.some((line) => line.includes("Added by macro"))).toBe(true);
1651
+ expect(buffer.lines.some((line) => line.includes("Child checkbox") && line.includes("☒"))).toBe(true);
1652
+ $("#deep").show();
1653
+ expect(buffer.lines.some((line) => line.includes("Deep checkbox") && line.includes("☒"))).toBe(true);
1654
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1655
+ });
1656
+
1657
+ test("restores direct fence body edits by block order and identity", () => {
1658
+ const source = "```text#editor\noriginal\n```\n";
1659
+ const buffer = {
1660
+ lines: source.split("\n"),
1661
+ _mdcuiTuiSourceText: source,
1662
+ _mdcuiAnsiText: source,
1663
+ cursor: { x: 0, y: 0 },
1664
+ ensureCursor() {},
1665
+ invalidateHighlightFrom() {},
1666
+ };
1667
+ buffer.lines[1] = "user input";
1668
+ buffer._mdcuiAnsiText = buffer.lines.join("\n");
1669
+ const snapshot = captureTuiRerenderState(buffer);
1670
+
1671
+ buffer.lines = source.split("\n");
1672
+ buffer._mdcuiAnsiText = source;
1673
+ restoreTuiRerenderState(buffer, snapshot);
1674
+
1675
+ expect(buffer.lines[1]).toBe("user input");
1676
+ expect(buffer._mdcuiRerenderMismatch).toBeNull();
1677
+ });
1678
+ });
1679
+
1680
+ class TestText {
1681
+ constructor(text, ownerDocument) {
1682
+ this.textContent = String(text);
1683
+ this.ownerDocument = ownerDocument;
1684
+ this.parentElement = null;
1685
+ }
1686
+ }
1687
+
1688
+ class TestElement {
1689
+ constructor(tagName, ownerDocument) {
1690
+ this.tagName = tagName.toUpperCase();
1691
+ this.ownerDocument = ownerDocument;
1692
+ this.parentElement = null;
1693
+ this.childNodes = [];
1694
+ this.classList = new Set();
1695
+ this.id = "";
1696
+ }
1697
+
1698
+ get children() {
1699
+ return this.childNodes.filter((node) => node instanceof TestElement);
1700
+ }
1701
+
1702
+ get firstChild() {
1703
+ return this.childNodes[0] ?? null;
1704
+ }
1705
+
1706
+ get nextElementSibling() {
1707
+ const siblings = this.parentElement?.children ?? [];
1708
+ const index = siblings.indexOf(this);
1709
+ return index >= 0 ? siblings[index + 1] ?? null : null;
1710
+ }
1711
+
1712
+ get textContent() {
1713
+ return this.childNodes.map((node) => node.textContent).join("");
1714
+ }
1715
+
1716
+ set textContent(value) {
1717
+ this.childNodes = [];
1718
+ this.append(this.ownerDocument.createTextNode(value));
1719
+ }
1720
+
1721
+ append(...nodes) {
1722
+ for (const node of nodes) {
1723
+ node.parentElement = this;
1724
+ this.childNodes.push(node);
1725
+ }
1726
+ }
1727
+
1728
+ insertBefore(node, before) {
1729
+ node.parentElement = this;
1730
+ const index = before == null ? -1 : this.childNodes.indexOf(before);
1731
+ if (index < 0) this.childNodes.push(node);
1732
+ else this.childNodes.splice(index, 0, node);
1733
+ }
1734
+
1735
+ remove() {
1736
+ const siblings = this.parentElement?.childNodes;
1737
+ const index = siblings?.indexOf(this) ?? -1;
1738
+ if (index >= 0) siblings.splice(index, 1);
1739
+ this.parentElement = null;
1740
+ }
1741
+
1742
+ matches(selector) {
1743
+ if (selector === "ul, ol") return this.tagName === "UL" || this.tagName === "OL";
1744
+ if (selector === "li.task-list-item")
1745
+ return this.tagName === "LI" && this.classList.has("task-list-item");
1746
+ return false;
1747
+ }
1748
+
1749
+ closest(selector) {
1750
+ for (let node = this; node; node = node.parentElement) {
1751
+ if (selector === "label" && node.tagName === "LABEL") return node;
1752
+ if (selector === "li.task-list-item" && node.matches(selector)) return node;
1753
+ }
1754
+ return null;
1755
+ }
1756
+
1757
+ querySelector(selector) {
1758
+ return this.querySelectorAll(selector)[0] ?? null;
1759
+ }
1760
+
1761
+ querySelectorAll(selector) {
1762
+ const matches = [];
1763
+ const visit = (node) => {
1764
+ for (const child of node.children) {
1765
+ if (selector === 'input[type="checkbox"]' &&
1766
+ child.tagName === "INPUT" && child.type === "checkbox") matches.push(child);
1767
+ else if (selector === "img" && child.tagName === "IMG") matches.push(child);
1768
+ else if (selector === "ul, ol" && child.matches(selector)) matches.push(child);
1769
+ else if (selector === "li.task-list-item" && child.matches(selector)) matches.push(child);
1770
+ visit(child);
1771
+ }
1772
+ };
1773
+ visit(this);
1774
+ return matches;
1775
+ }
1776
+ }
1777
+
1778
+ describe("WUI heading image selection", () => {
1779
+ test("img(index).src reads actual image elements before the next heading section", () => {
1780
+ const documentObject = new TestDocument();
1781
+ const section = documentObject.createElement("section");
1782
+ const heading = documentObject.createElement("h1");
1783
+ heading.id = "gallery";
1784
+ const body = documentObject.createElement("p");
1785
+ const first = documentObject.createElement("img");
1786
+ const second = documentObject.createElement("img");
1787
+ first.src = "https://example.test/one.png";
1788
+ second.src = "https://example.test/two.png";
1789
+ body.append(first, second);
1790
+ const childSection = documentObject.createElement("section");
1791
+ const childHeading = documentObject.createElement("h2");
1792
+ childHeading.id = "child";
1793
+ const childImage = documentObject.createElement("img");
1794
+ childImage.src = "https://example.test/child.png";
1795
+ childSection.append(childHeading, childImage);
1796
+ section.append(heading, body, childSection);
1797
+ documentObject.root.append(section);
1798
+ const $ = createWebDollar(documentObject);
1799
+
1800
+ expect($("#gallery").img(0).src).toBe("https://example.test/one.png");
1801
+ expect($("#gallery").img(1).src).toBe("https://example.test/two.png");
1802
+ expect($("#gallery").img(2).src).toBe("");
1803
+ expect($("#child").img(0).src).toBe("https://example.test/child.png");
1804
+ });
1805
+ });
1806
+
1807
+ describe("heading scrollIntoView", () => {
1808
+ test("TUI maps browser booleans and numeric alignment shortcuts", () => {
1809
+ const ansi = String(Bun.markdown.ansi("# First\n\nBody\n\n## Target\n", {
1810
+ hyperlinks: true,
1811
+ columns: 80,
1812
+ }));
1813
+ const buffer = {
1814
+ lines: Bun.stripANSI(ansi).split("\n"),
1815
+ _mdcuiTuiSourceText: "# First\n\nBody\n\n## Target\n",
1816
+ _mdcuiAnsiText: ansi,
1817
+ cursor: { x: 0, y: 0 },
1818
+ ensureCursor() {},
1819
+ };
1820
+ indexTuiHeadingRows(buffer);
1821
+ const blocks = [];
1822
+ const $ = createTuiSelector(
1823
+ () => buffer,
1824
+ null,
1825
+ ({ block }) => blocks.push(block),
1826
+ );
1827
+ const target = $("#target");
1828
+
1829
+ target.scrollIntoView();
1830
+ target.scrollIntoView(true);
1831
+ target.scrollIntoView(false);
1832
+ target.scrollIntoView(-1);
1833
+ target.scrollIntoView(0);
1834
+ target.scrollIntoView(1);
1835
+ target.scrollIntoView("anything", "ignored");
1836
+
1837
+ expect(blocks).toEqual([
1838
+ "start", "start", "end", "start", "center", "end", "center",
1839
+ ]);
1840
+ expect(buffer.lines[buffer.cursor.y]).toContain("Target");
1841
+ });
1842
+
1843
+ test("WUI forwards browser forms and translates numeric shortcuts", () => {
1844
+ const { $, heading } = webSelector();
1845
+ const calls = [];
1846
+ heading.scrollIntoView = (...args) => calls.push(args);
1847
+ const features = $("#features");
1848
+
1849
+ features.scrollIntoView();
1850
+ features.scrollIntoView(true);
1851
+ features.scrollIntoView(false);
1852
+ features.scrollIntoView(-1);
1853
+ features.scrollIntoView(0);
1854
+ features.scrollIntoView(1);
1855
+ features.scrollIntoView({ block: "start" });
1856
+
1857
+ expect(calls).toEqual([
1858
+ [],
1859
+ [true],
1860
+ [false],
1861
+ [true],
1862
+ [{ block: "center", inline: "nearest" }],
1863
+ [false],
1864
+ [{ block: "center", inline: "nearest" }],
1865
+ ]);
1866
+ });
1867
+ });
1868
+
1869
+ class TestDocument {
1870
+ constructor() {
1871
+ this.root = new TestElement("main", this);
1872
+ }
1873
+
1874
+ createElement(tagName) {
1875
+ return new TestElement(tagName, this);
1876
+ }
1877
+
1878
+ createTextNode(text) {
1879
+ return new TestText(text, this);
1880
+ }
1881
+
1882
+ getElementById(id) {
1883
+ return this.querySelectorAll("heading").find((element) => element.id === id) ?? null;
1884
+ }
1885
+
1886
+ querySelector(selector) {
1887
+ if (!selector.startsWith("#")) return null;
1888
+ const id = selector.slice(1);
1889
+ return this.querySelectorAll("heading").find((element) => element.id === id) ?? null;
1890
+ }
1891
+
1892
+ querySelectorAll(selector) {
1893
+ if (selector === "[data-mdcui-tag]" || selector === "pre > code") return [];
1894
+ if (selector === "heading") {
1895
+ const headings = [];
1896
+ const visit = (node) => {
1897
+ for (const child of node.children) {
1898
+ if (/^H[1-6]$/.test(child.tagName)) headings.push(child);
1899
+ visit(child);
1900
+ }
1901
+ };
1902
+ visit(this.root);
1903
+ return headings;
1904
+ }
1905
+ return [];
1906
+ }
1907
+ }
1908
+
1909
+ function appendWebItem(documentObject, list, value, checked = false) {
1910
+ const item = documentObject.createElement("li");
1911
+ item.classList.add("task-list-item");
1912
+ const label = documentObject.createElement("label");
1913
+ const checkbox = documentObject.createElement("input");
1914
+ checkbox.type = "checkbox";
1915
+ checkbox.checked = checked;
1916
+ label.append(checkbox, documentObject.createTextNode(value));
1917
+ item.append(label);
1918
+ list.append(item);
1919
+ return item;
1920
+ }
1921
+
1922
+ function webSelector() {
1923
+ const documentObject = new TestDocument();
1924
+ const section = documentObject.createElement("section");
1925
+ const heading = documentObject.createElement("h2");
1926
+ heading.id = "features";
1927
+ heading.append(documentObject.createTextNode("Features"));
1928
+ const list = documentObject.createElement("ul");
1929
+ appendWebItem(documentObject, list, "Search", true);
1930
+ appendWebItem(documentObject, list, "Notifications");
1931
+ appendWebItem(documentObject, list, "Offline", true);
1932
+ section.append(heading, list);
1933
+ documentObject.root.append(section);
1934
+ return { $: createWebDollar(documentObject), documentObject, section, heading, list };
1935
+ }
1936
+
1937
+ function nestedWebSelector() {
1938
+ const documentObject = new TestDocument();
1939
+ const outerSection = documentObject.createElement("section");
1940
+ const outerHeading = documentObject.createElement("h1");
1941
+ outerHeading.id = "first";
1942
+ outerHeading.append(documentObject.createTextNode("First"));
1943
+ const outerBody = documentObject.createElement("p");
1944
+ outerBody.append(documentObject.createTextNode("First web body."));
1945
+ const childSection = documentObject.createElement("section");
1946
+ const childHeading = documentObject.createElement("h2");
1947
+ childHeading.id = "child";
1948
+ childHeading.append(documentObject.createTextNode("Child"));
1949
+ const childBody = documentObject.createElement("p");
1950
+ childBody.append(documentObject.createTextNode("Child web body."));
1951
+ childSection.append(childHeading, childBody);
1952
+ outerSection.append(outerHeading, outerBody, childSection);
1953
+ documentObject.root.append(outerSection);
1954
+ return {
1955
+ $: createWebDollar(documentObject),
1956
+ documentObject,
1957
+ outerSection,
1958
+ outerHeading,
1959
+ childSection,
1960
+ childHeading,
1961
+ childBody,
1962
+ };
1963
+ }
1964
+
1965
+ function webTableSelector() {
1966
+ const documentObject = new TestDocument();
1967
+ const section = documentObject.createElement("section");
1968
+ const heading = documentObject.createElement("h2");
1969
+ heading.id = "table-with-id";
1970
+ heading.append(documentObject.createTextNode("Table With Id"));
1971
+ const table = documentObject.createElement("table");
1972
+ const header = documentObject.createElement("tr");
1973
+ const name = documentObject.createElement("th");
1974
+ const action = documentObject.createElement("th");
1975
+ name.append(documentObject.createTextNode("Name"));
1976
+ action.append(documentObject.createTextNode("Action"));
1977
+ header.append(name, action);
1978
+ const body = documentObject.createElement("tr");
1979
+ const item = documentObject.createElement("td");
1980
+ const linked = documentObject.createElement("td");
1981
+ const anchor = documentObject.createElement("a");
1982
+ item.append(documentObject.createTextNode("item"));
1983
+ anchor.append(documentObject.createTextNode("open"));
1984
+ linked.append(anchor);
1985
+ body.append(item, linked);
1986
+ table.append(header, body);
1987
+ section.append(heading, table);
1988
+ documentObject.root.append(section);
1989
+ return {
1990
+ $: createWebDollar(documentObject),
1991
+ documentObject,
1992
+ heading,
1993
+ table,
1994
+ anchor,
1995
+ };
1996
+ }
1997
+
1998
+ describe("WUI heading-associated table cells", () => {
1999
+ test("cell navigation matches TUI coordinates and bounds", () => {
2000
+ const { $ } = webTableSelector();
2001
+ const cell = $("#table-with-id").cell(1, 0);
2002
+
2003
+ expect(cell.row).toBe(1);
2004
+ expect(cell.col).toBe(0);
2005
+ expect(cell.text()).toBe("item");
2006
+ expect(cell.rt().text()).toBe("open");
2007
+ expect(cell.up().text()).toBe("Name");
2008
+ expect(cell.left()).toBeNull();
2009
+ expect(cell.down()).toBeNull();
2010
+ });
2011
+
2012
+ test("link parent returns its cell and text replacement retains the anchor", () => {
2013
+ const { $, anchor } = webTableSelector();
2014
+ const parent = $(anchor).parent();
2015
+
2016
+ expect(parent.row).toBe(1);
2017
+ expect(parent.col).toBe(1);
2018
+ expect(parent.lt().text()).toBe("item");
2019
+ expect(parent.text("執行")).toBe(parent);
2020
+ expect(parent.text()).toBe("執行");
2021
+ expect(anchor.parentElement.textContent).toBe("執行");
2022
+ expect(anchor.parentElement.children[0]).toBe(anchor);
2023
+ });
2024
+
2025
+ test("cell val returns the first checkbox state or falls back to text", () => {
2026
+ const { $, documentObject, table } = webTableSelector();
2027
+ const checkbox = documentObject.createElement("input");
2028
+ checkbox.type = "checkbox";
2029
+ checkbox.checked = true;
2030
+ table.children[1].children[1].insertBefore(
2031
+ checkbox,
2032
+ table.children[1].children[1].children[0],
2033
+ );
2034
+
2035
+ expect($("#table-with-id").cell(1, 0).val()).toBe("item");
2036
+ expect($("#table-with-id").cell(1, 1).val()).toBe(true);
2037
+ checkbox.checked = false;
2038
+ expect($("#table-with-id").cell(1, 1).val()).toBe(false);
2039
+ const checkedCell = $("#table-with-id").cell(1, 1);
2040
+ expect(checkedCell.val(true)).toBe(checkedCell);
2041
+ expect(checkedCell.val()).toBe(true);
2042
+ expect($("#table-with-id").cell(1, 0).val(false).text()).toBe("item");
2043
+ expect($("#table-with-id").cell(99, 0).val()).toBe("");
2044
+ });
2045
+ });
2046
+
2047
+ describe("WUI heading task-list Array methods", () => {
2048
+ test("uses the first list and follows Array return values", () => {
2049
+ const { $ } = webSelector();
2050
+ const features = $("#features");
2051
+
2052
+ expect(features.push("Export", { value: "Sync", checked: true })).toBe(5);
2053
+ expect(features.val()).toEqual(["Search", "Offline", "Sync"]);
2054
+ expect(features.unshift("First", "Second")).toBe(7);
2055
+ expect(features.shift()).toBe("First");
2056
+ expect(features.pop()).toBe("Sync");
2057
+ expect(features.val()).toEqual(["Search", "Offline"]);
2058
+ });
2059
+
2060
+ test("an empty first list remains the target", () => {
2061
+ const { $, list } = webSelector();
2062
+ const features = $("#features");
2063
+
2064
+ expect(features.shift()).toBe("Search");
2065
+ expect(features.shift()).toBe("Notifications");
2066
+ expect(features.shift()).toBe("Offline");
2067
+ expect(features.shift()).toBeUndefined();
2068
+ expect(features.val()).toEqual([]);
2069
+ expect(features.push({ label: "Again", checked: true })).toBe(1);
2070
+ expect(list.children).toHaveLength(1);
2071
+ expect(features.val()).toEqual(["Again"]);
2072
+ });
2073
+
2074
+ test("splice follows Array indexing and returns removed labels", () => {
2075
+ const { $ } = webSelector();
2076
+ const features = $("#features");
2077
+
2078
+ expect(features.splice()).toEqual([]);
2079
+ expect(features.splice(1, 1, { value: "Replacement", checked: true }, "Extra"))
2080
+ .toEqual(["Notifications"]);
2081
+ expect(features.val()).toEqual(["Search", "Replacement", "Offline"]);
2082
+ expect(features.splice(-1)).toEqual(["Offline"]);
2083
+ expect(features.splice(0, Infinity)).toEqual(["Search", "Replacement", "Extra"]);
2084
+ expect(features.splice(0, 0, { value: "Again", checked: true })).toEqual([]);
2085
+ expect(features.val()).toEqual(["Again"]);
2086
+ });
2087
+
2088
+ test("slice returns read-only item snapshots with checked state", () => {
2089
+ const { $ } = webSelector();
2090
+ const features = $("#features");
2091
+
2092
+ const result = features.slice(0, 2);
2093
+ expect(result).toEqual([
2094
+ { value: "Search", checked: true },
2095
+ { value: "Notifications", checked: false },
2096
+ ]);
2097
+ result[0].value = "Changed snapshot";
2098
+ result[0].checked = false;
2099
+ expect(features.slice(-1)).toEqual([{ value: "Offline", checked: true }]);
2100
+ expect(features.val()).toEqual(["Search", "Offline"]);
2101
+ });
2102
+ });
2103
+
2104
+ describe("WUI heading section visibility", () => {
2105
+ test("hide promotes the heading and hides its section", () => {
2106
+ const { $, documentObject, section, heading } = webSelector();
2107
+ const features = $("#features");
2108
+
2109
+ expect(features.hide()).toBe(features);
2110
+ expect(documentObject.root.children).toEqual([heading, section]);
2111
+ expect(heading.parentElement).toBe(documentObject.root);
2112
+ expect(section.hidden).toBe(true);
2113
+ expect(documentObject._mdcuiIdStore.get("features").headingVisibility.section).toBe(section);
2114
+ });
2115
+
2116
+ test("show restores the heading as the section's first item", () => {
2117
+ const { $, documentObject, section, heading, list } = webSelector();
2118
+ const features = $("#features");
2119
+
2120
+ features.hide().show();
2121
+ expect(documentObject.root.children).toEqual([section]);
2122
+ expect(section.children).toEqual([heading, list]);
2123
+ expect(section.firstChild).toBe(heading);
2124
+ expect(section.hidden).toBe(false);
2125
+ expect(documentObject._mdcuiIdStore.has("features")).toBe(false);
2126
+ });
2127
+
2128
+ test("toggle switches between the promoted and restored states", () => {
2129
+ const { $, section, heading } = webSelector();
2130
+ const features = $("#features");
2131
+
2132
+ features.toggle();
2133
+ expect(section.hidden).toBe(true);
2134
+ expect(heading.parentElement).not.toBe(section);
2135
+ features.toggle();
2136
+ expect(section.hidden).toBe(false);
2137
+ expect(heading.parentElement).toBe(section);
2138
+ });
2139
+
2140
+ test("show, hide, and toggle update a child while its parent is hidden", () => {
2141
+ const scenarios = [
2142
+ { initiallyHidden: false, method: "hide", visible: false },
2143
+ { initiallyHidden: true, method: "show", visible: true },
2144
+ { initiallyHidden: false, method: "toggle", visible: false },
2145
+ { initiallyHidden: true, method: "toggle", visible: true },
2146
+ ];
2147
+
2148
+ for (const scenario of scenarios) {
2149
+ const {
2150
+ $, documentObject, outerSection, childSection, childHeading,
2151
+ } = nestedWebSelector();
2152
+ const first = $("#first");
2153
+ const child = $("#child");
2154
+ if (scenario.initiallyHidden) child.hide();
2155
+ first.hide();
2156
+
2157
+ child[scenario.method]();
2158
+
2159
+ expect(outerSection.hidden).toBe(true);
2160
+ expect(documentObject._mdcuiIdStore.get("first").headingVisibility.hidden)
2161
+ .toBe(true);
2162
+ first.show();
2163
+ expect(Boolean(childSection.hidden)).toBe(!scenario.visible);
2164
+ expect(Boolean(
2165
+ documentObject._mdcuiIdStore.get("child")?.headingVisibility?.hidden,
2166
+ )).toBe(!scenario.visible);
2167
+ expect(childHeading.parentElement === childSection).toBe(scenario.visible);
2168
+ }
2169
+ });
2170
+ });
2171
+
2172
+ describe("WUI id-centered user data", () => {
2173
+ test("document store and DOM expose the exact same data object", () => {
2174
+ const { $, documentObject, heading } = webSelector();
2175
+ const features = $("#features");
2176
+ const data = features.data();
2177
+
2178
+ data.count = 1;
2179
+ expect(features.data()).toBe(data);
2180
+ expect(documentObject._mdcuiIdStore.get("features").data).toBe(data);
2181
+ expect(heading.mdcuiData).toBe(data);
2182
+ expect(features.data("count")).toBe(1);
2183
+ });
2184
+
2185
+ test("document data survives DOM removal and reattaches to a replacement", () => {
2186
+ const { $, documentObject, heading } = webSelector();
2187
+ const features = $("#features");
2188
+ const model = { ready: true };
2189
+ features.data("model", model);
2190
+
2191
+ heading.remove();
2192
+ expect(features.data("model")).toBe(model);
2193
+
2194
+ const replacement = documentObject.createElement("h2");
2195
+ replacement.id = "features";
2196
+ documentObject.root.append(replacement);
2197
+ expect(features.data()).toBe(documentObject._mdcuiIdStore.get("features").data);
2198
+ expect(replacement.mdcuiData).toBe(features.data());
2199
+ });
2200
+
2201
+ test("removeData supports keys and keeps framework visibility state", () => {
2202
+ const { $, documentObject, heading } = webSelector();
2203
+ const features = $("#features");
2204
+
2205
+ features.data({ count: 1, label: "x" }).hide();
2206
+ features.removeData(["count", "label"]);
2207
+ expect(features.data()).toEqual({});
2208
+ features.data("temporary", true).removeData();
2209
+ expect(heading.mdcuiData).toBeUndefined();
2210
+ expect(documentObject._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
2211
+ });
2212
+
2213
+ test("selection ids allow a selection to be wrapped again", () => {
2214
+ const { $, documentObject, heading } = webSelector();
2215
+ const features = $("#features");
2216
+ const nested = $($($(features)));
2217
+
2218
+ expect(features.id).toBe("features");
2219
+ expect(nested.id).toBe("features");
2220
+ features.data("count", 1);
2221
+ expect(nested.data()).toBe(features.data());
2222
+ expect(nested.text()).toBe(heading.textContent);
2223
+ nested.hide();
2224
+ expect(documentObject._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
2225
+ });
2226
+
2227
+ test("plain id objects resolve headings and nested data survives DOM removal", () => {
2228
+ const { $, documentObject, heading } = webSelector();
2229
+ const byId = $({ id: "features" });
2230
+ const nested = $($($("#features")));
2231
+
2232
+ expect(byId.text()).toBe(heading.textContent);
2233
+ byId.data("count", 2);
2234
+ expect($("#features").data("count")).toBe(2);
2235
+ byId.hide();
2236
+ expect(documentObject._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
2237
+ byId.show();
2238
+ expect(documentObject._mdcuiIdStore.get("features").headingVisibility).toBeUndefined();
2239
+
2240
+ heading.remove();
2241
+ expect(nested.data("count")).toBe(2);
2242
+ nested.data("afterRemoval", true);
2243
+ expect(documentObject._mdcuiIdStore.get("features").data.afterRemoval).toBe(true);
2244
+ });
2245
+
2246
+ test("legal object ids discard decoy fields through arbitrary nesting", () => {
2247
+ const { $, heading } = webSelector();
2248
+ const objectTarget = {
2249
+ id: "features",
2250
+ innerHTML: "DECOY HTML",
2251
+ textContent: "DECOY TEXT",
2252
+ value: "DECOY VALUE",
2253
+ };
2254
+ const direct = $(objectTarget);
2255
+ expect(direct.text()).toBe(heading.textContent);
2256
+ expect(direct.val()).toEqual(["Search", "Offline"]);
2257
+ objectTarget.id = "missing";
2258
+ let nested = direct;
2259
+ for (let depth = 0; depth < 8; depth++) nested = $(nested);
2260
+
2261
+ expect(nested.id).toBe("features");
2262
+ expect(nested.text()).toBe(heading.textContent);
2263
+ expect(nested.val()).toEqual(["Search", "Offline"]);
2264
+ expect(nested.val("Ignored")).toBe(nested);
2265
+ expect(heading.textContent).toBe("Features");
2266
+ expect(objectTarget.value).toBe("DECOY VALUE");
2267
+ expect(nested.push({ value: "Nested addition", checked: true })).toBe(4);
2268
+ expect(nested.val()).toEqual(["Search", "Offline", "Nested addition"]);
2269
+ });
2270
+
2271
+ test("Unicode and numeric element IDs remain canonical through object wrapping", () => {
2272
+ const { $, heading } = webSelector();
2273
+ heading.classList.add("field");
2274
+
2275
+ for (const id of ["中文-設定", "2026"]) {
2276
+ heading.id = id;
2277
+ let nested = $({ id, value: "decoy" });
2278
+ for (let depth = 0; depth < 4; depth++) nested = $(nested);
2279
+ expect(nested.id).toBe(id);
2280
+ expect(nested.text()).toBe("Features");
2281
+ expect(nested.data()).toBe($(`#${id}`).data());
2282
+ expect($(`h2#${id}.field`).text()).toBe("Features");
2283
+ expect($(`#${id}.field`).text()).toBe("Features");
2284
+ }
2285
+ });
2286
+ });
2287
+
2288
+ describe("WUI object target wrapper", () => {
2289
+ test("supports $(this).text() and $(event.target).text()", () => {
2290
+ const { $, documentObject } = webSelector();
2291
+ const link = documentObject.createElement("a");
2292
+ link.append(documentObject.createTextNode("Save"));
2293
+ const wrapped = $(link);
2294
+
2295
+ expect(wrapped.text()).toBe("Save");
2296
+ expect(wrapped.text("Saved")).toBe(wrapped);
2297
+ expect(link.textContent).toBe("Saved");
2298
+ expect($(link).text()).toBe("Saved");
2299
+ });
2300
+
2301
+ test("val uses an object's value property or falls back to textContent", () => {
2302
+ const { $, documentObject } = webSelector();
2303
+ const input = documentObject.createElement("input");
2304
+ input.value = "old";
2305
+ const link = documentObject.createElement("a");
2306
+ link.append(documentObject.createTextNode("Open"));
2307
+
2308
+ expect($(input).val()).toBe("old");
2309
+ $(input).val("new");
2310
+ expect(input.value).toBe("new");
2311
+ expect($(link).val()).toBe("Open");
2312
+ $(link).val("Close");
2313
+ expect(link.textContent).toBe("Close");
2314
+ });
2315
+
2316
+ test("heading html hides the internal first-character toggle wrapper", () => {
2317
+ const { $ } = webSelector();
2318
+ const copy = {
2319
+ innerHTML: '<em><span class="mdcui-heading-toggle">T</span>itle</em>',
2320
+ querySelectorAll() {
2321
+ return [{
2322
+ childNodes: ["T"],
2323
+ replaceWith() {
2324
+ copy.innerHTML = "<em>Title</em>";
2325
+ },
2326
+ }];
2327
+ },
2328
+ };
2329
+ const heading = {
2330
+ tagName: "H2",
2331
+ cloneNode() {
2332
+ return copy;
2333
+ },
2334
+ };
2335
+
2336
+ expect($(heading).html()).toBe("<em>Title</em>");
2337
+ });
2338
+
2339
+ test("invalid object ids stay generic while missing legal ids do not", () => {
2340
+ const { $ } = webSelector();
2341
+ const invalidTargets = [
2342
+ { id: "-invalid", value: "leading punctuation" },
2343
+ { id: "invalid id", value: "spaced" },
2344
+ ];
2345
+ const missing = { id: "missing", value: "decoy", textContent: "decoy" };
2346
+
2347
+ for (const target of invalidTargets) {
2348
+ const wrapped = $(target);
2349
+ expect(wrapped.id).toBe("");
2350
+ expect(wrapped.val()).toBe(target.value);
2351
+ wrapped.val("updated");
2352
+ expect(target.value).toBe("updated");
2353
+ }
2354
+ expect($(missing).val()).toBe("");
2355
+ $(missing).val("ignored");
2356
+ expect(missing.value).toBe("decoy");
2357
+ expect(missing.textContent).toBe("decoy");
2358
+ });
2359
+
2360
+ test("pure ids with selector punctuation use id lookup", () => {
2361
+ const documentObject = new TestDocument();
2362
+ const section = documentObject.createElement("section");
2363
+ const heading = documentObject.createElement("h2");
2364
+ heading.id = "feature:item";
2365
+ heading.append(documentObject.createTextNode("Punctuation ID"));
2366
+ section.append(heading);
2367
+ documentObject.root.append(section);
2368
+ documentObject.querySelector = () => {
2369
+ throw new SyntaxError("CSS parser treats ':' as a pseudo-class");
2370
+ };
2371
+ const $ = createWebDollar(documentObject);
2372
+
2373
+ expect($("#feature:item").text()).toBe("Punctuation ID");
2374
+ expect($({ id: "feature:item", textContent: "DECOY" }).text())
2375
+ .toBe("Punctuation ID");
2376
+ });
2377
+
2378
+ test("a missing literal colon ID cannot fall through to a CSS pseudo-selector", () => {
2379
+ const documentObject = new TestDocument();
2380
+ const section = documentObject.createElement("section");
2381
+ const heading = documentObject.createElement("h2");
2382
+ heading.id = "feature";
2383
+ heading.append(documentObject.createTextNode("Wrong CSS match"));
2384
+ section.append(heading);
2385
+ documentObject.root.append(section);
2386
+ documentObject.querySelector = selector =>
2387
+ selector === "#feature:first-child" ? heading : null;
2388
+ const $ = createWebDollar(documentObject);
2389
+
2390
+ expect($("#feature:first-child").id).toBe("feature:first-child");
2391
+ expect($("#feature:first-child").text()).toBe("");
2392
+ });
2393
+ });