@hyperpuncher/pi-ui 0.38.2

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 (448) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/npm/server-main.js +633 -0
  4. package/package.json +77 -0
  5. package/static/app/attachment-file.js +99 -0
  6. package/static/app/basecoat.js +57 -0
  7. package/static/app/code-copy.js +32 -0
  8. package/static/app/dialogs.js +153 -0
  9. package/static/app/display-refresh.js +125 -0
  10. package/static/app/display-refresh_test.ts +86 -0
  11. package/static/app/file-links.js +69 -0
  12. package/static/app/file-links_test.ts +13 -0
  13. package/static/app/file-transfer.js +463 -0
  14. package/static/app/file-transfer_test.ts +174 -0
  15. package/static/app/main.js +122 -0
  16. package/static/app/message-scroll.js +343 -0
  17. package/static/app/message-scroll_test.ts +28 -0
  18. package/static/app/model-search.js +37 -0
  19. package/static/app/model-search_test.ts +31 -0
  20. package/static/app/pickers.js +289 -0
  21. package/static/app/pickers_test.ts +40 -0
  22. package/static/app/prompt-history.js +100 -0
  23. package/static/app/prompt-history_test.ts +51 -0
  24. package/static/app/prompt.js +99 -0
  25. package/static/app/session-performance.js +56 -0
  26. package/static/app/vim-scroll.js +161 -0
  27. package/static/app/window-focus.js +43 -0
  28. package/static/app/window-focus_test.ts +83 -0
  29. package/static/app.css +2 -0
  30. package/static/basecoat.vendor.js +1 -0
  31. package/static/build/chunks/abap-sc56dbra.js +1 -0
  32. package/static/build/chunks/actionscript-3-8wkk0yve.js +1 -0
  33. package/static/build/chunks/ada-wkh6yxhp.js +1 -0
  34. package/static/build/chunks/ahk-vk45ms9x.js +1 -0
  35. package/static/build/chunks/ahk2-ap9fnbvc.js +1 -0
  36. package/static/build/chunks/andromeeda-zy6mvqwb.js +1 -0
  37. package/static/build/chunks/angular-html-61dpttc3.js +1 -0
  38. package/static/build/chunks/angular-ts-hwtqv276.js +1 -0
  39. package/static/build/chunks/apache-750k8mss.js +1 -0
  40. package/static/build/chunks/apex-vpts0jxm.js +1 -0
  41. package/static/build/chunks/apl-jkh6rwt8.js +1 -0
  42. package/static/build/chunks/applescript-zb2jfj46.js +1 -0
  43. package/static/build/chunks/ara-4yjpz89z.js +1 -0
  44. package/static/build/chunks/asciidoc-5xj8s3ay.js +1 -0
  45. package/static/build/chunks/asm-x6ejak5x.js +1 -0
  46. package/static/build/chunks/astro-9cd9b7yr.js +1 -0
  47. package/static/build/chunks/aurora-x-gp6v3669.js +1 -0
  48. package/static/build/chunks/awk-mpdee6bv.js +1 -0
  49. package/static/build/chunks/ayu-dark-hbtcbdtr.js +1 -0
  50. package/static/build/chunks/ayu-light-m7rwwssv.js +1 -0
  51. package/static/build/chunks/ayu-mirage-5z71k1jj.js +1 -0
  52. package/static/build/chunks/ballerina-7c4wt1p4.js +1 -0
  53. package/static/build/chunks/bat-9778z743.js +1 -0
  54. package/static/build/chunks/beancount-xs0qfpeg.js +1 -0
  55. package/static/build/chunks/berry-pmbyf94p.js +1 -0
  56. package/static/build/chunks/bibtex-nnkgf3bz.js +1 -0
  57. package/static/build/chunks/bicep-vqhsh01p.js +1 -0
  58. package/static/build/chunks/bird2-za6bpr1n.js +1 -0
  59. package/static/build/chunks/blade-s6ew6vqn.js +1 -0
  60. package/static/build/chunks/bsl-z36cmzeg.js +1 -0
  61. package/static/build/chunks/c-kmp7y11e.js +1 -0
  62. package/static/build/chunks/c3-1g0cgjqv.js +1 -0
  63. package/static/build/chunks/cadence-a23a7f13.js +1 -0
  64. package/static/build/chunks/cairo-r6vd4awx.js +1 -0
  65. package/static/build/chunks/catppuccin-frappe-05b7n5ms.js +1 -0
  66. package/static/build/chunks/catppuccin-latte-c7x59n84.js +1 -0
  67. package/static/build/chunks/catppuccin-macchiato-xq6zfjrk.js +1 -0
  68. package/static/build/chunks/catppuccin-mocha-xreqmgt9.js +1 -0
  69. package/static/build/chunks/chapel-717mc8q1.js +1 -0
  70. package/static/build/chunks/clarity-g6gq1dzn.js +1 -0
  71. package/static/build/chunks/clojure-pm54sv44.js +1 -0
  72. package/static/build/chunks/cmake-f1skxqyd.js +1 -0
  73. package/static/build/chunks/cobol-mys3qens.js +1 -0
  74. package/static/build/chunks/code-theme-y49p9zss.js +5 -0
  75. package/static/build/chunks/codeowners-1ys5r05d.js +1 -0
  76. package/static/build/chunks/codeql-tdw0bqwy.js +1 -0
  77. package/static/build/chunks/coffee-t9hejzk2.js +1 -0
  78. package/static/build/chunks/common-lisp-9hjzm22t.js +1 -0
  79. package/static/build/chunks/coq-fjr6d63p.js +1 -0
  80. package/static/build/chunks/cpp-1r5860gr.js +1 -0
  81. package/static/build/chunks/crystal-a1jynptt.js +1 -0
  82. package/static/build/chunks/csharp-4zp5c1yn.js +1 -0
  83. package/static/build/chunks/css-j5q0mhnm.js +1 -0
  84. package/static/build/chunks/csv-a39nx9ry.js +1 -0
  85. package/static/build/chunks/cue-tg9vgj1q.js +1 -0
  86. package/static/build/chunks/cypher-p1j7vh0k.js +1 -0
  87. package/static/build/chunks/d-1f7cc6re.js +1 -0
  88. package/static/build/chunks/dark-plus-zvzcxs8s.js +1 -0
  89. package/static/build/chunks/dart-8jekq66r.js +1 -0
  90. package/static/build/chunks/dax-2aph6zqg.js +1 -0
  91. package/static/build/chunks/desktop-fqy4qx2w.js +1 -0
  92. package/static/build/chunks/diff-hqj2qf6m.js +1 -0
  93. package/static/build/chunks/docker-cht4a1ht.js +1 -0
  94. package/static/build/chunks/dotenv-txd2x2vy.js +1 -0
  95. package/static/build/chunks/dracula-6abcnchd.js +1 -0
  96. package/static/build/chunks/dracula-soft-zyfsdrw6.js +1 -0
  97. package/static/build/chunks/dream-maker-0gsm31aq.js +1 -0
  98. package/static/build/chunks/edge-9h036g3s.js +1 -0
  99. package/static/build/chunks/elixir-7pyn80vc.js +1 -0
  100. package/static/build/chunks/elm-62v0vnec.js +1 -0
  101. package/static/build/chunks/emacs-lisp-58kh6hv8.js +1 -0
  102. package/static/build/chunks/erb-947psegf.js +1 -0
  103. package/static/build/chunks/erlang-vbf5hbwd.js +1 -0
  104. package/static/build/chunks/everforest-dark-g7rhmq33.js +1 -0
  105. package/static/build/chunks/everforest-light-spn7qghr.js +1 -0
  106. package/static/build/chunks/fennel-h323g5dt.js +1 -0
  107. package/static/build/chunks/fish-7qbmmjce.js +1 -0
  108. package/static/build/chunks/fluent-3sdffhr3.js +1 -0
  109. package/static/build/chunks/fonts-pcnzbv4q.js +1 -0
  110. package/static/build/chunks/fortran-fixed-form-azemmg4k.js +1 -0
  111. package/static/build/chunks/fortran-free-form-5njt96mh.js +1 -0
  112. package/static/build/chunks/fsharp-9eex114p.js +1 -0
  113. package/static/build/chunks/gdresource-v8yzpggb.js +1 -0
  114. package/static/build/chunks/gdscript-1snw12w7.js +1 -0
  115. package/static/build/chunks/gdshader-fmknssp0.js +1 -0
  116. package/static/build/chunks/genie-4gwgqcc6.js +1 -0
  117. package/static/build/chunks/gherkin-vjt0p14p.js +1 -0
  118. package/static/build/chunks/git-commit-z15avpkz.js +1 -0
  119. package/static/build/chunks/git-rebase-wj5kdx44.js +1 -0
  120. package/static/build/chunks/github-dark-default-q5m3jmep.js +1 -0
  121. package/static/build/chunks/github-dark-dimmed-0v34f529.js +1 -0
  122. package/static/build/chunks/github-dark-high-contrast-62grsqa6.js +1 -0
  123. package/static/build/chunks/github-dark-r3j6b7er.js +1 -0
  124. package/static/build/chunks/github-light-default-xgca5we2.js +1 -0
  125. package/static/build/chunks/github-light-high-contrast-0ee3g9rt.js +1 -0
  126. package/static/build/chunks/github-light-z1qp6zt5.js +1 -0
  127. package/static/build/chunks/gleam-znte7hjc.js +1 -0
  128. package/static/build/chunks/glimmer-js-nph8hwdq.js +1 -0
  129. package/static/build/chunks/glimmer-ts-fzpx4w3e.js +1 -0
  130. package/static/build/chunks/glsl-mzc3yta4.js +1 -0
  131. package/static/build/chunks/gn-3cqh8dtz.js +1 -0
  132. package/static/build/chunks/gnuplot-r8spf6zm.js +1 -0
  133. package/static/build/chunks/go-ac27dc57.js +1 -0
  134. package/static/build/chunks/graphql-f6vq7p23.js +1 -0
  135. package/static/build/chunks/groovy-p8r8bd0c.js +1 -0
  136. package/static/build/chunks/gruvbox-dark-hard-0zvxnx0e.js +1 -0
  137. package/static/build/chunks/gruvbox-dark-medium-qvjwy81n.js +1 -0
  138. package/static/build/chunks/gruvbox-dark-soft-mhtpt38s.js +1 -0
  139. package/static/build/chunks/gruvbox-light-hard-nrwjbbts.js +1 -0
  140. package/static/build/chunks/gruvbox-light-medium-qbwkw432.js +1 -0
  141. package/static/build/chunks/gruvbox-light-soft-rmkm778k.js +1 -0
  142. package/static/build/chunks/hack-a2wva0c2.js +1 -0
  143. package/static/build/chunks/haml-hmfn055t.js +1 -0
  144. package/static/build/chunks/handlebars-54kh2jwn.js +1 -0
  145. package/static/build/chunks/haskell-vkvkgtsh.js +1 -0
  146. package/static/build/chunks/haxe-yk1p58qs.js +1 -0
  147. package/static/build/chunks/hcl-mq3znmzm.js +1 -0
  148. package/static/build/chunks/hjson-ma7dfjrp.js +1 -0
  149. package/static/build/chunks/hlsl-jcfmchrk.js +1 -0
  150. package/static/build/chunks/horizon-bright-9s4tt51x.js +1 -0
  151. package/static/build/chunks/horizon-fm23m9qj.js +1 -0
  152. package/static/build/chunks/houston-fx2psmvq.js +1 -0
  153. package/static/build/chunks/html-derivative-xw79bbzd.js +1 -0
  154. package/static/build/chunks/html-nawteed7.js +1 -0
  155. package/static/build/chunks/http-72kn3sp8.js +1 -0
  156. package/static/build/chunks/hurl-eve0v3e8.js +1 -0
  157. package/static/build/chunks/hxml-8pcfrsh9.js +1 -0
  158. package/static/build/chunks/hy-989jkx6q.js +1 -0
  159. package/static/build/chunks/imba-cm19z6hg.js +1 -0
  160. package/static/build/chunks/index-gt9ev6v8.js +518 -0
  161. package/static/build/chunks/ini-a7zcn1v0.js +1 -0
  162. package/static/build/chunks/java-mjc9q3md.js +1 -0
  163. package/static/build/chunks/javascript-n8j33v1m.js +1 -0
  164. package/static/build/chunks/jinja-a6q4x5tm.js +1 -0
  165. package/static/build/chunks/jison-nj6cbyz2.js +1 -0
  166. package/static/build/chunks/json-bvza2dkb.js +1 -0
  167. package/static/build/chunks/json5-a53a0ked.js +1 -0
  168. package/static/build/chunks/jsonc-5tyv9mjd.js +1 -0
  169. package/static/build/chunks/jsonl-prv0dcjv.js +1 -0
  170. package/static/build/chunks/jsonnet-5gdjahsx.js +1 -0
  171. package/static/build/chunks/jssm-mwsp2p7j.js +1 -0
  172. package/static/build/chunks/jsx-75v76p5q.js +1 -0
  173. package/static/build/chunks/julia-45twp29b.js +1 -0
  174. package/static/build/chunks/just-ct7367ec.js +1 -0
  175. package/static/build/chunks/kanagawa-dragon-fbqq2bhk.js +1 -0
  176. package/static/build/chunks/kanagawa-lotus-zgxv1mgm.js +1 -0
  177. package/static/build/chunks/kanagawa-wave-wedn0ksa.js +1 -0
  178. package/static/build/chunks/kdl-n6ez9ngv.js +1 -0
  179. package/static/build/chunks/kotlin-ghtz1y98.js +1 -0
  180. package/static/build/chunks/kusto-43yprh9t.js +1 -0
  181. package/static/build/chunks/laserwave-2fz4nxs5.js +1 -0
  182. package/static/build/chunks/latex-httjryda.js +1 -0
  183. package/static/build/chunks/lean-49ww85t0.js +1 -0
  184. package/static/build/chunks/less-47m6fdq2.js +1 -0
  185. package/static/build/chunks/light-plus-5tghbj4b.js +1 -0
  186. package/static/build/chunks/liquid-r4gqgph0.js +1 -0
  187. package/static/build/chunks/llvm-zesbzx6h.js +1 -0
  188. package/static/build/chunks/log-n0g1tx1y.js +1 -0
  189. package/static/build/chunks/logo-j5ze60v0.js +1 -0
  190. package/static/build/chunks/lua-15pct4hw.js +1 -0
  191. package/static/build/chunks/luau-75hm4mxn.js +1 -0
  192. package/static/build/chunks/main-02cnngb2.js +2 -0
  193. package/static/build/chunks/main-06tnp3sh.js +2 -0
  194. package/static/build/chunks/main-0a9vnbak.js +2 -0
  195. package/static/build/chunks/main-0b281zv9.js +2 -0
  196. package/static/build/chunks/main-0nb87rgr.js +2 -0
  197. package/static/build/chunks/main-0w2eqgkm.js +2 -0
  198. package/static/build/chunks/main-1n8jg8rf.js +2 -0
  199. package/static/build/chunks/main-1q6650cv.js +2 -0
  200. package/static/build/chunks/main-1swy7s7h.js +2 -0
  201. package/static/build/chunks/main-1xeedcwa.js +2 -0
  202. package/static/build/chunks/main-2xnnvrcx.js +2 -0
  203. package/static/build/chunks/main-2zhgrhtb.js +2 -0
  204. package/static/build/chunks/main-3r9xmt0z.js +2 -0
  205. package/static/build/chunks/main-3s35k63z.js +2 -0
  206. package/static/build/chunks/main-3srxnktc.js +2 -0
  207. package/static/build/chunks/main-4sa6jbv9.js +2 -0
  208. package/static/build/chunks/main-61mzprk0.js +9 -0
  209. package/static/build/chunks/main-689tj0r4.js +2 -0
  210. package/static/build/chunks/main-6e7tjcht.js +2 -0
  211. package/static/build/chunks/main-6hjptvkk.js +2 -0
  212. package/static/build/chunks/main-6v87p7j0.js +2 -0
  213. package/static/build/chunks/main-77tx7a8q.js +2 -0
  214. package/static/build/chunks/main-7fchxg57.js +7 -0
  215. package/static/build/chunks/main-83kpz2vm.js +2 -0
  216. package/static/build/chunks/main-8vy6mdxc.js +2 -0
  217. package/static/build/chunks/main-98b1m2e8.js +2 -0
  218. package/static/build/chunks/main-9hkj1fvj.js +2 -0
  219. package/static/build/chunks/main-9r4kp1re.js +2 -0
  220. package/static/build/chunks/main-9rd1xskg.js +2 -0
  221. package/static/build/chunks/main-9rh1hxyw.js +2 -0
  222. package/static/build/chunks/main-9v5c2hm4.js +2 -0
  223. package/static/build/chunks/main-aw75bv70.js +2 -0
  224. package/static/build/chunks/main-b1wctdb9.js +2 -0
  225. package/static/build/chunks/main-brrygrcd.js +2 -0
  226. package/static/build/chunks/main-bxkfh3s9.js +2 -0
  227. package/static/build/chunks/main-capt12kv.js +2 -0
  228. package/static/build/chunks/main-cbea0k4t.js +2 -0
  229. package/static/build/chunks/main-cs7592x8.js +2 -0
  230. package/static/build/chunks/main-d04rg092.js +2 -0
  231. package/static/build/chunks/main-d4pbngvb.js +2 -0
  232. package/static/build/chunks/main-d96ygjmk.js +2 -0
  233. package/static/build/chunks/main-dbxf9vvb.js +2 -0
  234. package/static/build/chunks/main-e0bf1hc7.js +2 -0
  235. package/static/build/chunks/main-e1qp4v0e.js +2 -0
  236. package/static/build/chunks/main-ed67cb17.js +2 -0
  237. package/static/build/chunks/main-ef0zm6td.js +2 -0
  238. package/static/build/chunks/main-epkkzhsq.js +2 -0
  239. package/static/build/chunks/main-f1t9zy21.js +2 -0
  240. package/static/build/chunks/main-g4ry2z87.js +2 -0
  241. package/static/build/chunks/main-h9p54b6w.js +2 -0
  242. package/static/build/chunks/main-hp9qyk20.js +2 -0
  243. package/static/build/chunks/main-hv1v7a2c.js +2 -0
  244. package/static/build/chunks/main-jr2gz2ea.js +2 -0
  245. package/static/build/chunks/main-kktqjawn.js +2 -0
  246. package/static/build/chunks/main-knre9pdj.js +2 -0
  247. package/static/build/chunks/main-m9qkdc8e.js +2 -0
  248. package/static/build/chunks/main-mcptmf3e.js +2 -0
  249. package/static/build/chunks/main-mmmydrw7.js +2 -0
  250. package/static/build/chunks/main-mqmtpd6m.js +2 -0
  251. package/static/build/chunks/main-n8b99y0n.js +2 -0
  252. package/static/build/chunks/main-nex51cmy.js +2 -0
  253. package/static/build/chunks/main-nzkzztjw.js +2 -0
  254. package/static/build/chunks/main-p5c2apac.js +2 -0
  255. package/static/build/chunks/main-p8efbnm1.js +2 -0
  256. package/static/build/chunks/main-q9q129d1.js +2 -0
  257. package/static/build/chunks/main-qbf61mtn.js +2 -0
  258. package/static/build/chunks/main-rb1nkcm0.js +2 -0
  259. package/static/build/chunks/main-spgna5r3.js +2 -0
  260. package/static/build/chunks/main-tqpmccz6.js +2 -0
  261. package/static/build/chunks/main-v7n0f428.js +2 -0
  262. package/static/build/chunks/main-v9j8veta.js +2 -0
  263. package/static/build/chunks/main-w5g9070a.js +2 -0
  264. package/static/build/chunks/main-wbpws0v9.js +2 -0
  265. package/static/build/chunks/main-wcnadmhs.js +1530 -0
  266. package/static/build/chunks/main-x61973bh.js +2 -0
  267. package/static/build/chunks/main-xqc8f31a.js +2 -0
  268. package/static/build/chunks/main-xtaq3qt4.js +2 -0
  269. package/static/build/chunks/main-xvgwadfa.js +2 -0
  270. package/static/build/chunks/main-y0dvfpw7.js +2 -0
  271. package/static/build/chunks/main-ynjqm60c.js +2 -0
  272. package/static/build/chunks/main-ypk8hvc0.js +2 -0
  273. package/static/build/chunks/main-zjsthwaz.js +2 -0
  274. package/static/build/chunks/main-zy79z3j0.js +2 -0
  275. package/static/build/chunks/make-82gdkt9b.js +1 -0
  276. package/static/build/chunks/markdown-5tkrg1ah.js +1 -0
  277. package/static/build/chunks/marko-qafrqcsm.js +1 -0
  278. package/static/build/chunks/material-theme-darker-fnv6smqw.js +1 -0
  279. package/static/build/chunks/material-theme-lighter-35th362e.js +1 -0
  280. package/static/build/chunks/material-theme-ocean-4dq3ahw3.js +1 -0
  281. package/static/build/chunks/material-theme-p7k291d1.js +1 -0
  282. package/static/build/chunks/material-theme-palenight-yhd4pmvh.js +1 -0
  283. package/static/build/chunks/matlab-mbnxwt6q.js +1 -0
  284. package/static/build/chunks/mdc-w29armgh.js +1 -0
  285. package/static/build/chunks/mdx-53tq2vwd.js +1 -0
  286. package/static/build/chunks/mermaid-kp8v57t9.js +1 -0
  287. package/static/build/chunks/min-dark-rtx0tqrz.js +1 -0
  288. package/static/build/chunks/min-light-5m66fg5h.js +1 -0
  289. package/static/build/chunks/mipsasm-may8rjf3.js +1 -0
  290. package/static/build/chunks/mojo-ezqmbs71.js +1 -0
  291. package/static/build/chunks/monokai-79awbcqn.js +1 -0
  292. package/static/build/chunks/moonbit-57vh5qgn.js +1 -0
  293. package/static/build/chunks/move-71jrpzz7.js +1 -0
  294. package/static/build/chunks/narrat-n4xxr8p6.js +1 -0
  295. package/static/build/chunks/nextflow-c0gn0hzt.js +1 -0
  296. package/static/build/chunks/nextflow-groovy-13xg49y5.js +1 -0
  297. package/static/build/chunks/nginx-vtb8rwdx.js +1 -0
  298. package/static/build/chunks/night-owl-light-05jd94v7.js +1 -0
  299. package/static/build/chunks/night-owl-y5p2e2yz.js +1 -0
  300. package/static/build/chunks/nim-nfvgqc30.js +1 -0
  301. package/static/build/chunks/nix-rkxmnaat.js +1 -0
  302. package/static/build/chunks/nord-akta5pm4.js +1 -0
  303. package/static/build/chunks/nsis-6c1fw7sy.js +1 -0
  304. package/static/build/chunks/nushell-pvtw3a6j.js +1 -0
  305. package/static/build/chunks/objective-c-jd7171g7.js +1 -0
  306. package/static/build/chunks/objective-cpp-6v7n9hk7.js +1 -0
  307. package/static/build/chunks/ocaml-kjv8t7w5.js +1 -0
  308. package/static/build/chunks/odin-58v0n0xa.js +1 -0
  309. package/static/build/chunks/one-dark-pro-pbp84hvh.js +1 -0
  310. package/static/build/chunks/one-light-jpadzfmm.js +1 -0
  311. package/static/build/chunks/openscad-0xnz3sah.js +1 -0
  312. package/static/build/chunks/org-xe4jzp20.js +1 -0
  313. package/static/build/chunks/pascal-f75emr3j.js +1 -0
  314. package/static/build/chunks/perl-zp07ased.js +1 -0
  315. package/static/build/chunks/php-fjm4srka.js +1 -0
  316. package/static/build/chunks/pierre-dark-2sng8cw6.js +1 -0
  317. package/static/build/chunks/pierre-dark-protanopia-deuteranopia-615mrd5v.js +1 -0
  318. package/static/build/chunks/pierre-dark-soft-xpc8stwc.js +1 -0
  319. package/static/build/chunks/pierre-dark-tritanopia-nw71c9hw.js +1 -0
  320. package/static/build/chunks/pierre-dark-vibrant-dtc0vftx.js +1 -0
  321. package/static/build/chunks/pierre-light-mtkcs9xq.js +1 -0
  322. package/static/build/chunks/pierre-light-protanopia-deuteranopia-d84wh0sh.js +1 -0
  323. package/static/build/chunks/pierre-light-soft-f0vxkd08.js +1 -0
  324. package/static/build/chunks/pierre-light-tritanopia-prpe8fpj.js +1 -0
  325. package/static/build/chunks/pierre-light-vibrant-y1syd51f.js +1 -0
  326. package/static/build/chunks/pkl-x0wayphd.js +1 -0
  327. package/static/build/chunks/plastic-5dg6r26t.js +1 -0
  328. package/static/build/chunks/plsql-259gvzn9.js +1 -0
  329. package/static/build/chunks/po-6kcxkqtd.js +1 -0
  330. package/static/build/chunks/poimandres-p8seewrs.js +1 -0
  331. package/static/build/chunks/polar-b1ev80vb.js +1 -0
  332. package/static/build/chunks/postcss-kxdxws93.js +1 -0
  333. package/static/build/chunks/powerquery-ved4pq1y.js +1 -0
  334. package/static/build/chunks/powershell-6j0w6ann.js +1 -0
  335. package/static/build/chunks/prisma-7r1mj2qq.js +1 -0
  336. package/static/build/chunks/prolog-7a8gwgvm.js +1 -0
  337. package/static/build/chunks/proto-s5e2jd7n.js +1 -0
  338. package/static/build/chunks/pug-8cxb8b6f.js +1 -0
  339. package/static/build/chunks/puppet-d1048c00.js +1 -0
  340. package/static/build/chunks/purescript-vqffe1a1.js +1 -0
  341. package/static/build/chunks/python-7z2eacwy.js +1 -0
  342. package/static/build/chunks/qml-51nrq0re.js +1 -0
  343. package/static/build/chunks/qmldir-q0jk1gzg.js +1 -0
  344. package/static/build/chunks/qss-6far86jw.js +1 -0
  345. package/static/build/chunks/r-qkm8ck5x.js +1 -0
  346. package/static/build/chunks/racket-mzbdy2fp.js +1 -0
  347. package/static/build/chunks/raku-se52tvjp.js +1 -0
  348. package/static/build/chunks/razor-y9b1j8td.js +1 -0
  349. package/static/build/chunks/rbs-00y9vc5r.js +1 -0
  350. package/static/build/chunks/red-tkj5bw1z.js +1 -0
  351. package/static/build/chunks/reg-9z4ax2nc.js +1 -0
  352. package/static/build/chunks/regexp-99svsrmp.js +1 -0
  353. package/static/build/chunks/rel-tf2jya2v.js +1 -0
  354. package/static/build/chunks/riscv-hpebp724.js +1 -0
  355. package/static/build/chunks/ron-s3mfhwnh.js +1 -0
  356. package/static/build/chunks/rose-pine-dawn-apmj5y1p.js +1 -0
  357. package/static/build/chunks/rose-pine-moon-mgx7zgjy.js +1 -0
  358. package/static/build/chunks/rose-pine-vgbee1jh.js +1 -0
  359. package/static/build/chunks/rosmsg-vgsgfpe4.js +1 -0
  360. package/static/build/chunks/rst-xnf3rrzd.js +1 -0
  361. package/static/build/chunks/ruby-gysj44ty.js +1 -0
  362. package/static/build/chunks/rust-bzv74qkx.js +1 -0
  363. package/static/build/chunks/sas-t4zftd7j.js +1 -0
  364. package/static/build/chunks/sass-tnsj7t5t.js +1 -0
  365. package/static/build/chunks/scala-vsjg5eg2.js +1 -0
  366. package/static/build/chunks/scheme-yhv7mnk9.js +1 -0
  367. package/static/build/chunks/scss-vt2fyer8.js +1 -0
  368. package/static/build/chunks/sdbl-jv670bae.js +1 -0
  369. package/static/build/chunks/shaderlab-csszkf9s.js +1 -0
  370. package/static/build/chunks/shellscript-t3jf42zg.js +1 -0
  371. package/static/build/chunks/shellsession-e1m2zj1m.js +1 -0
  372. package/static/build/chunks/slack-dark-ntnagayj.js +1 -0
  373. package/static/build/chunks/slack-ochin-kge218j8.js +1 -0
  374. package/static/build/chunks/smalltalk-q8bgts0p.js +1 -0
  375. package/static/build/chunks/smithy-m7r7jeg3.js +1 -0
  376. package/static/build/chunks/snazzy-light-57mzezsa.js +1 -0
  377. package/static/build/chunks/solarized-dark-0r80rpr7.js +1 -0
  378. package/static/build/chunks/solarized-light-1e0p6z46.js +1 -0
  379. package/static/build/chunks/solidity-vb1zcsry.js +1 -0
  380. package/static/build/chunks/soy-bzh2aynk.js +1 -0
  381. package/static/build/chunks/sparql-6t8g6rdd.js +1 -0
  382. package/static/build/chunks/splunk-50bpjgv6.js +1 -0
  383. package/static/build/chunks/sql-kkaf039q.js +1 -0
  384. package/static/build/chunks/ssh-config-715bkrac.js +1 -0
  385. package/static/build/chunks/stata-zhvjkw4g.js +1 -0
  386. package/static/build/chunks/stylus-b8aj4z6s.js +1 -0
  387. package/static/build/chunks/surrealql-4w27b1xf.js +1 -0
  388. package/static/build/chunks/svelte-6vty1ayf.js +1 -0
  389. package/static/build/chunks/swift-2w2k3des.js +1 -0
  390. package/static/build/chunks/synthwave-84-007ndkbp.js +1 -0
  391. package/static/build/chunks/system-verilog-hzg0fpex.js +1 -0
  392. package/static/build/chunks/systemd-d3k4d1av.js +1 -0
  393. package/static/build/chunks/talonscript-fw295t4h.js +1 -0
  394. package/static/build/chunks/tasl-8n3br9kn.js +1 -0
  395. package/static/build/chunks/tcl-e485436v.js +1 -0
  396. package/static/build/chunks/templ-535cbmg6.js +1 -0
  397. package/static/build/chunks/terraform-k8aqghnt.js +1 -0
  398. package/static/build/chunks/tex-9yx3fnmk.js +1 -0
  399. package/static/build/chunks/tokyo-night-rvkd4sna.js +1 -0
  400. package/static/build/chunks/toml-bypjcbh1.js +1 -0
  401. package/static/build/chunks/ts-tags-zyfv802m.js +1 -0
  402. package/static/build/chunks/tsv-2n58sktq.js +1 -0
  403. package/static/build/chunks/tsx-gnkw8sgq.js +1 -0
  404. package/static/build/chunks/turtle-sf93b8kj.js +1 -0
  405. package/static/build/chunks/twig-8gw76pn3.js +1 -0
  406. package/static/build/chunks/typescript-077c9w1h.js +1 -0
  407. package/static/build/chunks/typespec-8r8ncprb.js +1 -0
  408. package/static/build/chunks/typst-yg6690gs.js +1 -0
  409. package/static/build/chunks/v-tdjz29z1.js +1 -0
  410. package/static/build/chunks/vala-trffggt0.js +1 -0
  411. package/static/build/chunks/vb-1dx16kyy.js +1 -0
  412. package/static/build/chunks/verilog-sv7hb60t.js +1 -0
  413. package/static/build/chunks/vesper-005phtef.js +1 -0
  414. package/static/build/chunks/vhdl-qck3dqgh.js +1 -0
  415. package/static/build/chunks/viml-m54vetp0.js +1 -0
  416. package/static/build/chunks/vitesse-black-sxfaa2kz.js +1 -0
  417. package/static/build/chunks/vitesse-dark-fhxvesec.js +1 -0
  418. package/static/build/chunks/vitesse-light-cygqrsgb.js +1 -0
  419. package/static/build/chunks/vue-html-5ex4htv4.js +1 -0
  420. package/static/build/chunks/vue-n8p10w4p.js +1 -0
  421. package/static/build/chunks/vue-vine-y2qnwmqn.js +1 -0
  422. package/static/build/chunks/vyper-r9v6ymtz.js +1 -0
  423. package/static/build/chunks/wasm-c0aw8acq.js +1 -0
  424. package/static/build/chunks/wasm-eftreyx0.js +1 -0
  425. package/static/build/chunks/wenyan-pn35fr2w.js +1 -0
  426. package/static/build/chunks/wgsl-akmafmem.js +1 -0
  427. package/static/build/chunks/wikitext-yp9d6pan.js +1 -0
  428. package/static/build/chunks/wit-rg9t2t2j.js +1 -0
  429. package/static/build/chunks/wolfram-m5vswcfg.js +1 -0
  430. package/static/build/chunks/workspace-review-3scrbyaw.js +1361 -0
  431. package/static/build/chunks/xml-8pftb6y7.js +1 -0
  432. package/static/build/chunks/xsl-w4bvqyjy.js +1 -0
  433. package/static/build/chunks/yaml-a56qt88c.js +1 -0
  434. package/static/build/chunks/zenscript-pp2pffj8.js +1 -0
  435. package/static/build/chunks/zig-53aewpf9.js +1 -0
  436. package/static/build/main.js +6 -0
  437. package/static/build/pierre-worker.js +156 -0
  438. package/static/favicon.svg +1 -0
  439. package/static/file-uri.js +18 -0
  440. package/static/file-uri_test.ts +35 -0
  441. package/static/icon-180.png +0 -0
  442. package/static/icon-192.png +0 -0
  443. package/static/icon-512.png +0 -0
  444. package/static/manifest.webmanifest +23 -0
  445. package/static/notification-icon.png +0 -0
  446. package/static/theme.js +17 -0
  447. package/static/vendor/datastar-inspector.min.js +1021 -0
  448. package/static/vendor/datastar.js +10 -0
@@ -0,0 +1,174 @@
1
+ import { test } from "bun:test";
2
+
3
+ import { assertEquals, assertRejects } from "#testing/assertions";
4
+
5
+ import { attachmentFileKind } from "./attachment-file.js";
6
+
7
+ Object.defineProperty(globalThis, "ResizeObserver", {
8
+ configurable: true,
9
+ value: class {
10
+ disconnect() {
11
+ return undefined;
12
+ }
13
+ observe() {
14
+ return undefined;
15
+ }
16
+ },
17
+ });
18
+
19
+ const {
20
+ composePrompt,
21
+ convertAvifToJpeg,
22
+ extractTransferredFilePaths,
23
+ formatFileReferences,
24
+ isAvifImageFile,
25
+ isHeicImageFile,
26
+ jpegFileName,
27
+ } = await import("./file-transfer.js");
28
+
29
+ test("file references use one line per path and end with a newline", () => {
30
+ assertEquals(
31
+ formatFileReferences(["/tmp/one.txt", "/tmp/two.txt"]),
32
+ "@/tmp/one.txt\n@/tmp/two.txt\n",
33
+ );
34
+ });
35
+
36
+ test("attachment paths are composed separately from visible prompt editing", () => {
37
+ assertEquals(
38
+ composePrompt("review these", ["/tmp/one.txt", "/tmp/two.txt"]),
39
+ "@/tmp/one.txt\n@/tmp/two.txt\nreview these",
40
+ );
41
+ assertEquals(composePrompt("", ["/tmp/one.txt"]), "@/tmp/one.txt");
42
+ });
43
+
44
+ test("transferred files use their original paths", () => {
45
+ const values = new Map(
46
+ Object.entries({
47
+ "text/uri-list": "# files\nfile:///tmp/one.txt\nfile:///tmp/two%20words.txt",
48
+ "x-special/gnome-copied-files":
49
+ "copy\nfile:///tmp/one.txt\nfile:///tmp/three.txt",
50
+ "text/plain": "/tmp/four.txt\nnot-a-path",
51
+ }),
52
+ );
53
+
54
+ assertEquals(
55
+ extractTransferredFilePaths({
56
+ getData: (type: string) => values.get(type) ?? "",
57
+ }),
58
+ ["/tmp/one.txt", "/tmp/two words.txt", "/tmp/three.txt", "/tmp/four.txt"],
59
+ );
60
+ });
61
+
62
+ test("attachment file kinds use MIME types and extensions", () => {
63
+ assertEquals(attachmentFileKind("vadim.txt", "text/plain"), "text");
64
+ assertEquals(attachmentFileKind("recording.ogg", "audio/ogg"), "audio");
65
+ assertEquals(attachmentFileKind("source.ts", ""), "code");
66
+ assertEquals(attachmentFileKind("bundle.zip", ""), "archive");
67
+ assertEquals(attachmentFileKind("unknown.bin", ""), "file");
68
+ });
69
+
70
+ test("AVIF images are detected and renamed for JPEG conversion", () => {
71
+ assertEquals(isAvifImageFile({ name: "photo.bin", type: "image/avif" }), true);
72
+ assertEquals(isAvifImageFile({ name: "photo.AVIF", type: "" }), true);
73
+ assertEquals(isAvifImageFile({ name: "photo.png", type: "image/png" }), false);
74
+ assertEquals(jpegFileName("photo.AVIF"), "photo.jpg");
75
+ assertEquals(jpegFileName("pasted-image"), "pasted-image.jpg");
76
+ });
77
+
78
+ test("HEIC and HEIF images are detected by MIME type or extension", () => {
79
+ assertEquals(isHeicImageFile({ name: "photo.bin", type: "image/heic" }), true);
80
+ assertEquals(isHeicImageFile({ name: "photo.HEIF", type: "" }), true);
81
+ assertEquals(isHeicImageFile({ name: "photo.avif", type: "image/avif" }), false);
82
+ });
83
+
84
+ test("AVIF conversion produces a quality 85 JPEG and closes the bitmap", async () => {
85
+ const originalBitmap = Object.getOwnPropertyDescriptor(
86
+ globalThis,
87
+ "createImageBitmap",
88
+ );
89
+ const originalDocument = Object.getOwnPropertyDescriptor(globalThis, "document");
90
+ let closed = false;
91
+ const canvas = {
92
+ width: 0,
93
+ height: 0,
94
+ getContext: () => ({
95
+ fillStyle: "",
96
+ fillRect: () => undefined,
97
+ drawImage: () => undefined,
98
+ }),
99
+ toBlob: (callback: (blob: Blob) => void, type: string, quality: number) => {
100
+ assertEquals(type, "image/jpeg");
101
+ assertEquals(quality, 0.85);
102
+ callback(new Blob(["jpeg"], { type }));
103
+ },
104
+ };
105
+ Object.defineProperty(globalThis, "createImageBitmap", {
106
+ configurable: true,
107
+ value: () =>
108
+ Promise.resolve({
109
+ width: 3200,
110
+ height: 1800,
111
+ close: () => {
112
+ closed = true;
113
+ },
114
+ }),
115
+ });
116
+ Object.defineProperty(globalThis, "document", {
117
+ configurable: true,
118
+ value: { createElement: () => canvas },
119
+ });
120
+ try {
121
+ const converted = await convertAvifToJpeg(
122
+ new File(["avif"], "screenshot.avif", {
123
+ type: "image/avif",
124
+ lastModified: 42,
125
+ }),
126
+ );
127
+ assertEquals(converted.name, "screenshot.jpg");
128
+ assertEquals(converted.type, "image/jpeg");
129
+ assertEquals(converted.lastModified, 42);
130
+ assertEquals(canvas.width, 3200);
131
+ assertEquals(canvas.height, 1800);
132
+ assertEquals(closed, true);
133
+ } finally {
134
+ restoreGlobal("createImageBitmap", originalBitmap);
135
+ restoreGlobal("document", originalDocument);
136
+ }
137
+ });
138
+
139
+ test("AVIF conversion reports decoding failures clearly", async () => {
140
+ const original = Object.getOwnPropertyDescriptor(globalThis, "createImageBitmap");
141
+ Object.defineProperty(globalThis, "createImageBitmap", {
142
+ configurable: true,
143
+ value: () => Promise.reject(new Error("decode failed")),
144
+ });
145
+ try {
146
+ await assertRejects(
147
+ () =>
148
+ convertAvifToJpeg(
149
+ new File(["broken"], "broken.avif", { type: "image/avif" }),
150
+ ),
151
+ Error,
152
+ "Could not convert broken.avif to JPEG.",
153
+ );
154
+ } finally {
155
+ restoreGlobal("createImageBitmap", original);
156
+ }
157
+ });
158
+
159
+ function restoreGlobal(name: string, descriptor?: PropertyDescriptor) {
160
+ if (descriptor) {
161
+ Object.defineProperty(globalThis, name, descriptor);
162
+ } else {
163
+ Reflect.deleteProperty(globalThis, name);
164
+ }
165
+ }
166
+
167
+ test("transferred files use a webview-provided path without reading bytes", () => {
168
+ assertEquals(
169
+ extractTransferredFilePaths({
170
+ files: [{ path: "/tmp/large-model.bin", name: "large-model.bin" }],
171
+ }),
172
+ ["/tmp/large-model.bin"],
173
+ );
174
+ });
@@ -0,0 +1,122 @@
1
+ import { fuzzyFilter, fuzzyMatch } from "../../src/client/pi-fuzzy.ts";
2
+ import { refresh } from "./basecoat.js";
3
+ import { bindCodeCopy } from "./code-copy.js";
4
+ import * as dialogs from "./dialogs.js";
5
+ import { bindDisplayRefreshMeasurement } from "./display-refresh.js";
6
+ import { bindFileLinks } from "./file-links.js";
7
+ import * as fileTransfer from "./file-transfer.js";
8
+ import {
9
+ bindMessageResize,
10
+ bindMessageScroll,
11
+ captureAnchor,
12
+ hydratePierreDiff,
13
+ restoreAnchor,
14
+ scrollBottom,
15
+ } from "./message-scroll.js";
16
+ import { filterModelSearch } from "./model-search.js";
17
+ import {
18
+ bindPickers,
19
+ closePickers,
20
+ isFileOpen,
21
+ isOpen as isPickerOpen,
22
+ resetFilePicker,
23
+ } from "./pickers.js";
24
+ import { createPromptHistory } from "./prompt-history.js";
25
+ import {
26
+ bindPromptInteractions,
27
+ bindPromptLayout,
28
+ focusPromptEnd,
29
+ setPromptValue,
30
+ } from "./prompt.js";
31
+ import {
32
+ readTransitionState,
33
+ startSessionPerformanceMeasurement,
34
+ } from "./session-performance.js";
35
+ import { bindVimScroll } from "./vim-scroll.js";
36
+ import { windowFocus } from "./window-focus.js";
37
+
38
+ const promptHistory = createPromptHistory();
39
+
40
+ window.piUi = {
41
+ basecoat: { refresh },
42
+ codeTheme: { loadPreviews() {} },
43
+ fonts: { apply() {} },
44
+ dialogs,
45
+ fileTransfer,
46
+ messageScroll: {
47
+ bindResize: bindMessageResize,
48
+ captureAnchor,
49
+ hydratePierreDiff,
50
+ restoreAnchor,
51
+ scrollBottom,
52
+ },
53
+ modelSearch: { filter: filterModelSearch },
54
+ pickers: {
55
+ close: closePickers,
56
+ fuzzyMatch,
57
+ isFileOpen,
58
+ isOpen: isPickerOpen,
59
+ resetFile: resetFilePicker,
60
+ },
61
+ prompt: {
62
+ bindLayout: bindPromptLayout,
63
+ clear: () => setPromptValue(""),
64
+ },
65
+ promptHistory,
66
+ sessionPerformance: {
67
+ observe: readTransitionState,
68
+ start: startSessionPerformanceMeasurement,
69
+ },
70
+ windowFocus,
71
+ workspaceReview: { applyOpen: () => {} },
72
+ shouldAbortOnEscape(event) {
73
+ return !event.defaultPrevented && !hasOpenDismissible();
74
+ },
75
+ };
76
+
77
+ function hasOpenDismissible() {
78
+ if (isPickerOpen() || document.querySelector("dialog[open]")) return true;
79
+ if (document.querySelector('[data-popover][aria-hidden="false"]')) return true;
80
+ return Boolean(document.querySelector('[aria-haspopup][aria-expanded="true"]'));
81
+ }
82
+
83
+ // Register delegated file-link handling as soon as this module evaluates. Waiting for
84
+ // DOMContentLoaded makes it vulnerable to another initializer failing first and leaves
85
+ // the browser to attempt the forbidden file:// navigation itself.
86
+ bindFileLinks();
87
+
88
+ window.addEventListener("DOMContentLoaded", async () => {
89
+ await Promise.all([
90
+ import("../../src/client/fonts.ts"),
91
+ import("../../src/client/code-theme.ts"),
92
+ import("../../src/client/workspace-review.ts"),
93
+ ]);
94
+
95
+ focusPromptEnd();
96
+ dialogs.bindDialogs();
97
+ bindPromptInteractions();
98
+ bindPickers({ fuzzyFilter });
99
+ bindMessageScroll();
100
+ bindCodeCopy();
101
+ bindVimScroll();
102
+ bindDisplayRefreshMeasurement();
103
+ bindDebugFps();
104
+ });
105
+
106
+ function bindDebugFps() {
107
+ const fps = document.getElementById("debug-fps");
108
+ if (!(fps instanceof HTMLElement)) return;
109
+ let frames = 0;
110
+ let startedAt = performance.now();
111
+ const tick = (now) => {
112
+ frames += 1;
113
+ const elapsed = now - startedAt;
114
+ if (elapsed >= 500) {
115
+ fps.textContent = String(Math.round((frames * 1000) / elapsed));
116
+ frames = 0;
117
+ startedAt = now;
118
+ }
119
+ requestAnimationFrame(tick);
120
+ };
121
+ requestAnimationFrame(tick);
122
+ }
@@ -0,0 +1,343 @@
1
+ const liveEdgeThresholdPx = 8;
2
+ const promptSpacerClearancePx = 48;
3
+ const scrollControlThresholdPx = 48;
4
+ const state = {
5
+ middleScrolling: false,
6
+ pinnedToBottom: true,
7
+ pointerScrolling: false,
8
+ rearmOnBottom: false,
9
+ scrollTop: 0,
10
+ };
11
+ const bottomScrollTimers = new Set();
12
+ let anchor;
13
+ let historyLoading = false;
14
+ let observedMessageStack;
15
+ let observedPrompt;
16
+ let messageResizeObserver;
17
+ let pointerStart;
18
+
19
+ export function bindMessageScroll() {
20
+ document.addEventListener(
21
+ "scroll",
22
+ (event) => {
23
+ const messages = document.getElementById("messages");
24
+ // Ignore captured scroll events from nested tool and code outputs. Layout
25
+ // changes alone never re-arm following; reaching the end during an active
26
+ // downward wheel or scrollbar gesture does.
27
+ if (!(messages instanceof HTMLElement) || event.target !== messages) return;
28
+ if (
29
+ (state.middleScrolling || state.pointerScrolling) &&
30
+ messages.scrollTop < state.scrollTop
31
+ )
32
+ markUnpinned();
33
+ const distance =
34
+ messages.scrollHeight - messages.scrollTop - messages.clientHeight;
35
+ if (
36
+ shouldRearmAfterScroll(
37
+ state.pinnedToBottom,
38
+ state.scrollTop,
39
+ messages.scrollTop,
40
+ distance,
41
+ state.middleScrolling ||
42
+ state.rearmOnBottom ||
43
+ state.pointerScrolling,
44
+ )
45
+ )
46
+ state.pinnedToBottom = true;
47
+ state.scrollTop = messages.scrollTop;
48
+ state.rearmOnBottom = false;
49
+ updateScrollControl();
50
+ },
51
+ true,
52
+ );
53
+
54
+ // Release follow mode from explicit reader interactions, never from scroll
55
+ // position changes alone: streamed tables, code, and other blocks can resize.
56
+ const releasePointerScroll = (event) => {
57
+ // Browser autoscroll can begin outside the transcript's DOM event path,
58
+ // so middle-button intent is global. Primary drags inside the transcript
59
+ // cover scrollbar movement and text selection without treating clicks as scrolls.
60
+ if (event.button === 1) {
61
+ state.middleScrolling = true;
62
+ markUnpinned();
63
+ } else if (event.button === 0 && isMessageInteraction(event.target)) {
64
+ state.middleScrolling = false;
65
+ state.pointerScrolling = true;
66
+ pointerStart = { x: event.clientX, y: event.clientY };
67
+ const messages = document.getElementById("messages");
68
+ if (messages instanceof HTMLElement) state.scrollTop = messages.scrollTop;
69
+ }
70
+ };
71
+ document.addEventListener("pointerdown", releasePointerScroll, {
72
+ capture: true,
73
+ passive: true,
74
+ });
75
+ document.addEventListener(
76
+ "pointermove",
77
+ (event) => {
78
+ if (
79
+ state.pointerScrolling &&
80
+ pointerStart &&
81
+ hasPointerDragIntent(
82
+ pointerStart.x,
83
+ pointerStart.y,
84
+ event.clientX,
85
+ event.clientY,
86
+ )
87
+ ) {
88
+ pointerStart = undefined;
89
+ markUnpinned();
90
+ }
91
+ },
92
+ { capture: true, passive: true },
93
+ );
94
+ for (const type of ["pointerup", "pointercancel"]) {
95
+ document.addEventListener(
96
+ type,
97
+ () => {
98
+ state.pointerScrolling = false;
99
+ pointerStart = undefined;
100
+ },
101
+ { capture: true, passive: true },
102
+ );
103
+ }
104
+ for (const type of ["mousedown", "auxclick"]) {
105
+ document.addEventListener(
106
+ type,
107
+ (event) => {
108
+ if (event.button === 1) {
109
+ state.middleScrolling = true;
110
+ markUnpinned();
111
+ }
112
+ },
113
+ {
114
+ capture: true,
115
+ passive: true,
116
+ },
117
+ );
118
+ }
119
+ document.addEventListener(
120
+ "wheel",
121
+ (event) => {
122
+ if (!isMessageInteraction(event.target)) return;
123
+ state.middleScrolling = false;
124
+ if (event.deltaY < 0) markUnpinned();
125
+ else if (event.deltaY > 0) state.rearmOnBottom = true;
126
+ },
127
+ { capture: true, passive: true },
128
+ );
129
+ document.addEventListener(
130
+ "touchmove",
131
+ (event) => {
132
+ if (isMessageInteraction(event.target)) markUnpinned();
133
+ },
134
+ { capture: true, passive: true },
135
+ );
136
+ document.addEventListener(
137
+ "keydown",
138
+ (event) => {
139
+ if (isUpwardScrollKey(event) && isMessageInteraction(event.target))
140
+ markUnpinned();
141
+ },
142
+ true,
143
+ );
144
+
145
+ bindMessageResize();
146
+ scrollBottom();
147
+ }
148
+
149
+ export function captureAnchor() {
150
+ if (historyLoading) return false;
151
+ const messages = document.getElementById("messages");
152
+ if (!(messages instanceof HTMLElement)) return false;
153
+ const viewportTop = messages.getBoundingClientRect().top;
154
+ const visibleMessage = [...messages.querySelectorAll("[data-message-id]")].find(
155
+ (message) => message.getBoundingClientRect().bottom > viewportTop,
156
+ );
157
+ historyLoading = true;
158
+ anchor = {
159
+ pinnedToBottom: state.pinnedToBottom,
160
+ messageId: visibleMessage?.getAttribute("data-message-id"),
161
+ offset: visibleMessage
162
+ ? visibleMessage.getBoundingClientRect().top - viewportTop
163
+ : undefined,
164
+ scrollHeight: messages.scrollHeight,
165
+ scrollTop: messages.scrollTop,
166
+ };
167
+ updateScrollControl();
168
+ return true;
169
+ }
170
+
171
+ export function restoreAnchor() {
172
+ const saved = anchor;
173
+ anchor = undefined;
174
+ historyLoading = false;
175
+ if (!saved) return;
176
+ if (saved.pinnedToBottom) {
177
+ scrollBottom();
178
+ return;
179
+ }
180
+
181
+ // Restore against a retained DOM node rather than estimating from scrollHeight.
182
+ // Datastar morphs and deferred message rendering can both change unrelated heights.
183
+ const restore = () => {
184
+ const messages = document.getElementById("messages");
185
+ if (!(messages instanceof HTMLElement)) return;
186
+ const retainedMessage = [...messages.querySelectorAll("[data-message-id]")].find(
187
+ (message) => message.getAttribute("data-message-id") === saved.messageId,
188
+ );
189
+ if (retainedMessage && saved.offset !== undefined) {
190
+ const currentOffset =
191
+ retainedMessage.getBoundingClientRect().top -
192
+ messages.getBoundingClientRect().top;
193
+ messages.scrollTop = retainedAnchorScrollTop(
194
+ messages.scrollTop,
195
+ currentOffset,
196
+ saved.offset,
197
+ );
198
+ } else {
199
+ messages.scrollTop =
200
+ saved.scrollTop + messages.scrollHeight - saved.scrollHeight;
201
+ }
202
+ updateScrollControl();
203
+ };
204
+
205
+ // The immediate correction prevents a paint at the morphed position. Follow-up
206
+ // frames absorb layout produced by custom-element hydration and style resolution.
207
+ restore();
208
+ requestAnimationFrame(() => {
209
+ restore();
210
+ requestAnimationFrame(restore);
211
+ });
212
+ }
213
+
214
+ export function retainedAnchorScrollTop(scrollTop, currentOffset, savedOffset) {
215
+ return scrollTop + currentOffset - savedOffset;
216
+ }
217
+
218
+ export function hasPointerDragIntent(startX, startY, currentX, currentY) {
219
+ return Math.hypot(currentX - startX, currentY - startY) >= 8;
220
+ }
221
+
222
+ export function shouldRearmAfterScroll(
223
+ wasPinned,
224
+ previousTop,
225
+ scrollTop,
226
+ distance,
227
+ hasDownwardIntent,
228
+ ) {
229
+ return (
230
+ !wasPinned &&
231
+ hasDownwardIntent &&
232
+ scrollTop > previousTop &&
233
+ distance <= liveEdgeThresholdPx
234
+ );
235
+ }
236
+
237
+ export function scrollBottom(behavior = "auto") {
238
+ clearBottomScrollTimers();
239
+ anchor = undefined;
240
+ historyLoading = false;
241
+ state.middleScrolling = false;
242
+ state.pinnedToBottom = true;
243
+ state.pointerScrolling = false;
244
+ state.rearmOnBottom = false;
245
+ const scroll = () => {
246
+ const messages = document.getElementById("messages");
247
+ if (!(messages instanceof HTMLElement) || !state.pinnedToBottom) return;
248
+ messages.scrollTo({ top: messages.scrollHeight, behavior });
249
+ updateScrollControl();
250
+ };
251
+ scroll();
252
+ if (behavior === "auto") {
253
+ for (const delay of [16, 80, 180]) {
254
+ const timer = setTimeout(() => {
255
+ bottomScrollTimers.delete(timer);
256
+ scroll();
257
+ }, delay);
258
+ bottomScrollTimers.add(timer);
259
+ }
260
+ }
261
+ }
262
+
263
+ export function markUnpinned() {
264
+ clearBottomScrollTimers();
265
+ state.pinnedToBottom = false;
266
+ state.rearmOnBottom = false;
267
+ const messages = document.getElementById("messages");
268
+ if (messages instanceof HTMLElement) state.scrollTop = messages.scrollTop;
269
+ updateScrollControl();
270
+ }
271
+
272
+ function clearBottomScrollTimers() {
273
+ for (const timer of bottomScrollTimers) clearTimeout(timer);
274
+ bottomScrollTimers.clear();
275
+ }
276
+
277
+ function isMessageInteraction(target) {
278
+ const messages = document.getElementById("messages");
279
+ return (
280
+ messages instanceof HTMLElement &&
281
+ target instanceof Node &&
282
+ messages.contains(target)
283
+ );
284
+ }
285
+
286
+ function isUpwardScrollKey(event) {
287
+ return (
288
+ event.key === "ArrowUp" ||
289
+ event.key === "PageUp" ||
290
+ event.key === "Home" ||
291
+ (event.key === " " && event.shiftKey)
292
+ );
293
+ }
294
+
295
+ export function bindMessageResize() {
296
+ const stack = document.querySelector("#messages > .messages-stack");
297
+ const prompt = document.getElementById("prompt-box");
298
+ if (!(stack instanceof HTMLElement) || !(prompt instanceof HTMLElement)) return;
299
+ if (stack === observedMessageStack && prompt === observedPrompt) return;
300
+ messageResizeObserver ??= new ResizeObserver(() => {
301
+ updatePromptSpacer();
302
+ const messages = document.getElementById("messages");
303
+ if (messages instanceof HTMLElement && state.pinnedToBottom)
304
+ messages.scrollTop = messages.scrollHeight;
305
+ updateScrollControl();
306
+ });
307
+ if (observedMessageStack) messageResizeObserver.unobserve(observedMessageStack);
308
+ if (observedPrompt) messageResizeObserver.unobserve(observedPrompt);
309
+ observedMessageStack = stack;
310
+ observedPrompt = prompt;
311
+ messageResizeObserver.observe(stack);
312
+ messageResizeObserver.observe(prompt);
313
+ }
314
+
315
+ function updatePromptSpacer() {
316
+ const prompt = document.getElementById("prompt-box");
317
+ const spacer = document.getElementById("messages-prompt-spacer");
318
+ if (!(prompt instanceof HTMLElement) || !(spacer instanceof HTMLElement)) return;
319
+ const height = `${prompt.offsetHeight + promptSpacerClearancePx}px`;
320
+ if (spacer.style.height !== height) spacer.style.height = height;
321
+ }
322
+
323
+ function updateScrollControl() {
324
+ const messages = document.getElementById("messages");
325
+ const button = document.getElementById("messages-latest");
326
+ if (!(messages instanceof HTMLElement) || !(button instanceof HTMLButtonElement))
327
+ return;
328
+ const distance = messages.scrollHeight - messages.scrollTop - messages.clientHeight;
329
+ const active = !state.pinnedToBottom && distance >= scrollControlThresholdPx;
330
+ button.hidden = !active;
331
+ button.inert = !active;
332
+ button.tabIndex = active ? 0 : -1;
333
+ }
334
+
335
+ export function hydratePierreDiff(host) {
336
+ if (!(host instanceof HTMLElement)) return;
337
+ const template = host.querySelector('template[shadowrootmode="open"]');
338
+ if (!(template instanceof HTMLTemplateElement)) return;
339
+ // Pierre may create the shadow root before Datastar inserts its template.
340
+ const shadow = host.shadowRoot ?? host.attachShadow({ mode: "open" });
341
+ shadow.append(template.content.cloneNode(true));
342
+ template.remove();
343
+ }
@@ -0,0 +1,28 @@
1
+ import { test } from "bun:test";
2
+
3
+ import { assertEquals } from "#testing/assertions";
4
+
5
+ import {
6
+ hasPointerDragIntent,
7
+ retainedAnchorScrollTop,
8
+ shouldRearmAfterScroll,
9
+ } from "./message-scroll.js";
10
+
11
+ test("retained message anchor preserves its viewport offset", () => {
12
+ assertEquals(retainedAnchorScrollTop(240, 760, 40), 960);
13
+ assertEquals(retainedAnchorScrollTop(960, 40, 40), 960);
14
+ });
15
+
16
+ test("pointer presses require drag intent before releasing follow mode", () => {
17
+ assertEquals(hasPointerDragIntent(100, 100, 100, 100), false);
18
+ assertEquals(hasPointerDragIntent(100, 100, 107, 100), false);
19
+ assertEquals(hasPointerDragIntent(100, 100, 108, 100), true);
20
+ assertEquals(hasPointerDragIntent(100, 100, 106, 106), true);
21
+ });
22
+
23
+ test("downward user scrolling to the end re-arms following", () => {
24
+ assertEquals(shouldRearmAfterScroll(false, 400, 420, 8, true), true);
25
+ assertEquals(shouldRearmAfterScroll(false, 400, 420, 8.1, true), false);
26
+ assertEquals(shouldRearmAfterScroll(false, 420, 400, 0, true), false);
27
+ assertEquals(shouldRearmAfterScroll(false, 400, 420, 0, false), false);
28
+ });
@@ -0,0 +1,37 @@
1
+ import { fuzzyFilter } from "@earendil-works/pi-tui/dist/fuzzy.js";
2
+
3
+ export function filterModelSearch(input, query) {
4
+ if (!(input instanceof HTMLInputElement)) return;
5
+ const command = input.closest(".command");
6
+ if (!(command instanceof HTMLElement)) return;
7
+ const items = [...command.querySelectorAll('[role="menuitem"]')].filter(
8
+ (item) => item instanceof HTMLElement,
9
+ );
10
+ const originalItems = items.toSorted(
11
+ (first, second) =>
12
+ Number(first.dataset.modelSearchOrder) -
13
+ Number(second.dataset.modelSearchOrder),
14
+ );
15
+ const matches = fuzzyFilter(originalItems, query, (item) =>
16
+ modelSearchText(item.dataset.filter ?? "", item.dataset.keywords ?? ""),
17
+ );
18
+ const visible = new Set(matches);
19
+ const orderedItems = query.trim()
20
+ ? [...matches, ...originalItems.filter((item) => !visible.has(item))]
21
+ : originalItems;
22
+
23
+ for (const item of orderedItems) {
24
+ // Basecoat 1.0.2 tracks the active item by index. Reordering before
25
+ // refresh() can otherwise leave the old item active as well.
26
+ item.classList.remove("active");
27
+ item.setAttribute("aria-hidden", String(!visible.has(item)));
28
+ item.parentElement?.append(item);
29
+ }
30
+ command.refresh?.();
31
+ }
32
+
33
+ export function modelSearchText(modelAndProvider, name) {
34
+ // Preserve camel-case boundaries that pi's case-insensitive matcher cannot see.
35
+ const expandedName = name.replace(/([\p{Ll}\d])(\p{Lu})/gu, "$1 $2");
36
+ return `${expandedName} ${modelAndProvider} ${name}`;
37
+ }