buninu-browser 0.2.4 → 0.4.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 (249) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +102 -2
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +123 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +116 -0
  20. package/frontends/wayland/input.js +56 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +158 -0
  26. package/index.js +28 -1
  27. package/lib/clipboard-command.js +10 -0
  28. package/lib/clipboard-powershell.js +22 -0
  29. package/lib/clipboard.js +57 -0
  30. package/lib/document-types.js +11 -0
  31. package/lib/headless-shell.js +78 -13
  32. package/lib/layout/text-layout.js +17 -9
  33. package/lib/paint/display-list.js +5 -1
  34. package/lib/paint/frame.js +35 -3
  35. package/lib/paint/raw-frames.js +47 -0
  36. package/lib/qml/README.md +971 -0
  37. package/lib/qml/builtins.js +700 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/document.js +1176 -0
  40. package/lib/qml/format.js +9 -0
  41. package/lib/qml/host.js +324 -0
  42. package/lib/qml/images.js +199 -0
  43. package/lib/qml/launcher.js +132 -0
  44. package/lib/qml/loader.js +517 -0
  45. package/lib/qml/media-renderer.js +49 -0
  46. package/lib/qml/media.js +86 -0
  47. package/lib/qml/models.js +622 -0
  48. package/lib/qml/parser.js +381 -0
  49. package/lib/qml/qs +5 -0
  50. package/lib/qml/qscasty +3 -0
  51. package/lib/qml/qsheadless +3 -0
  52. package/lib/qml/qswayland +3 -0
  53. package/lib/qml/reactive.js +159 -0
  54. package/lib/qml/runtime.js +3381 -0
  55. package/lib/qml/shapes.js +243 -0
  56. package/lib/qml/text.js +109 -0
  57. package/lib/qml/textures.js +123 -0
  58. package/lib/qml/wayland-clipboard.js +32 -0
  59. package/lib/qml/worker-thread.js +70 -0
  60. package/lib/render-tree/index.js +16 -1
  61. package/lib/renderer/bindings.js +11 -0
  62. package/lib/renderer/canvas-2d.js +13 -0
  63. package/lib/renderer/compositor-channel.js +103 -0
  64. package/lib/renderer/compositor.js +25 -33
  65. package/lib/renderer/flags.js +1 -1
  66. package/lib/renderer/host.js +128 -92
  67. package/lib/renderer/main.js +38 -4
  68. package/lib/renderer/page-realm.js +32 -1
  69. package/lib/renderer/page-renderer.js +250 -21
  70. package/lib/renderer/text-input-paint.js +54 -0
  71. package/lib/renderer/text-input.js +57 -0
  72. package/lib/style/computed-style.js +2 -2
  73. package/package.json +1 -1
  74. package/test/clipboard.test.js +54 -0
  75. package/test/fixtures/browser-raw-demo.html +36 -0
  76. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  77. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  78. package/test/fixtures/flea-helpers/bin/bat +3 -0
  79. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  80. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  81. package/test/fixtures/flea-helpers/bin/glow +3 -0
  82. package/test/fixtures/flea-helpers/bin/qs +3 -0
  83. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  84. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  85. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  86. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  87. package/test/fixtures/flea-helpers/check.mjs +36 -0
  88. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  89. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  90. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  91. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  92. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  93. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  94. package/test/fixtures/qml/flea/LICENSE +21 -0
  95. package/test/fixtures/qml/flea/README.md +25 -0
  96. package/test/fixtures/qml/flea/Theme.qml +8 -0
  97. package/test/fixtures/qml/flea/demo.qml +24 -0
  98. package/test/fixtures/qml/flea/qmldir +3 -0
  99. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  100. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  101. package/test/fixtures/qml/flea-theme/README.md +70 -0
  102. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  103. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  104. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  105. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  106. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  107. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  108. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  109. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  110. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  111. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  112. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  113. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  114. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  115. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  116. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  117. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  118. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  119. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  120. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  121. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  122. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  123. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  124. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  125. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  126. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  127. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  128. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  129. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  130. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  131. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  132. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  133. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  134. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  135. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  136. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  137. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  138. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  139. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  140. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  141. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  142. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  143. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  144. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  145. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  146. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  147. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  148. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  173. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  174. package/test/fixtures/qml/hello.qml +26 -0
  175. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  176. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  177. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  178. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  179. package/test/fixtures/qml/imports/logic/base.js +2 -0
  180. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  181. package/test/fixtures/qml/imports/logic/math.js +3 -0
  182. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  183. package/test/fixtures/qml/imports/main.qml +21 -0
  184. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  185. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  186. package/test/fixtures/qml/multiply.qml +61 -0
  187. package/test/fixtures/qml-canvas-dom.js +90 -0
  188. package/test/fixtures/qml-media-audio.js +39 -0
  189. package/test/fixtures/raw-capture-benchmark.json +76 -0
  190. package/test/helpers/flea-sandbox.js +48 -0
  191. package/test/helpers/quickshell-fixture.js +125 -0
  192. package/test/html-clipboard-wayland.test.js +47 -0
  193. package/test/index.test.js +13 -0
  194. package/test/manual-casty-without-qml.js +87 -0
  195. package/test/manual-flea-application.js +49 -0
  196. package/test/manual-flea-binary.js +62 -0
  197. package/test/manual-flea-qml.js +9 -0
  198. package/test/manual-flea-settings.js +40 -0
  199. package/test/manual-flea-theme.js +40 -0
  200. package/test/manual-qml-removal.js +46 -0
  201. package/test/manual-without-qml.js +45 -0
  202. package/test/qml-canvas-dom.test.js +37 -0
  203. package/test/qml-clipboard-bridge.test.js +25 -0
  204. package/test/qml-clipboard-wayland.test.js +40 -0
  205. package/test/qml-connections-timing.test.js +73 -0
  206. package/test/qml-dynamic.test.js +401 -0
  207. package/test/qml-flea-app.test.js +146 -0
  208. package/test/qml-flea-backend.test.js +78 -0
  209. package/test/qml-flea-columns-backend.test.js +38 -0
  210. package/test/qml-flea-compile.test.js +78 -0
  211. package/test/qml-flea-launcher.test.js +88 -0
  212. package/test/qml-flea-markdown-backend.test.js +31 -0
  213. package/test/qml-flea-media.test.js +28 -0
  214. package/test/qml-flea-navigation-backend.test.js +35 -0
  215. package/test/qml-flea-navigation-timing.test.js +147 -0
  216. package/test/qml-flea-preview-backend.test.js +53 -0
  217. package/test/qml-flea-previews.test.js +163 -0
  218. package/test/qml-flea-rows.test.js +101 -0
  219. package/test/qml-flea-textures.test.js +59 -0
  220. package/test/qml-flea-view-switch.test.js +33 -0
  221. package/test/qml-flea-wayland.test.js +68 -0
  222. package/test/qml-flea.test.js +34 -0
  223. package/test/qml-host.test.js +238 -0
  224. package/test/qml-imports.test.js +102 -0
  225. package/test/qml-inherited-handlers.test.js +51 -0
  226. package/test/qml-input-selection.test.js +66 -0
  227. package/test/qml-interactions.test.js +309 -0
  228. package/test/qml-language.test.js +167 -0
  229. package/test/qml-launcher.test.js +54 -0
  230. package/test/qml-layout-text.test.js +205 -0
  231. package/test/qml-media.test.js +63 -0
  232. package/test/qml-persisted-delegates.test.js +45 -0
  233. package/test/qml-quickshell-controls.test.js +65 -0
  234. package/test/qml-quickshell-settings.test.js +62 -0
  235. package/test/qml-repeater.test.js +72 -0
  236. package/test/qml-style-updates.test.js +124 -0
  237. package/test/qml-textures.test.js +66 -0
  238. package/test/qml-views.test.js +332 -0
  239. package/test/qml-worker.test.js +69 -0
  240. package/test/qml.test.js +83 -0
  241. package/test/raw-capture.test.js +152 -0
  242. package/test/renderer-host.test.js +11 -0
  243. package/test/text-input-pointer.test.js +31 -0
  244. package/test/text-input-scroll.test.js +29 -0
  245. package/test/text-input.test.js +47 -0
  246. package/test/wayland-frontend.test.js +83 -0
  247. package/test/wayland-urlbar.test.js +38 -0
  248. package/test/window-resize.test.js +36 -0
  249. package/tools/bench-raw-capture.js +32 -0
@@ -284,8 +284,8 @@ const CONTROL_FONT = "font-size: 13.333px; font-family: sans-serif";
284
284
  const FIELD = `${CONTROL_FONT}; border: 1px solid #767676; background-color: white`;
285
285
  const BUTTON_LIKE = `${CONTROL_FONT}; border: 1px solid #767676; border-radius: 2px; padding: 1px 6px; background-color: #efefef; text-align: center`;
286
286
  const UA_CONTROL_DECLARATIONS = Object.freeze({
287
- textarea: parseDeclarations(`${FIELD}; font-family: monospace; padding: 2px; white-space: pre-wrap; overflow-wrap: break-word`),
288
- text: parseDeclarations(`${FIELD}; padding: 1px 2px; white-space: pre`),
287
+ textarea: parseDeclarations(`${FIELD}; font-family: monospace; padding: 2px; white-space: pre-wrap; overflow-wrap: break-word; overflow: auto`),
288
+ text: parseDeclarations(`${FIELD}; padding: 1px 2px; white-space: pre; overflow: hidden`),
289
289
  checkbox: parseDeclarations("width: 13px; height: 13px; margin: 3px 3px 3px 4px; border: 1px solid #767676; border-radius: 2px; background-color: white; box-sizing: border-box"),
290
290
  radio: parseDeclarations("width: 13px; height: 13px; margin: 3px 3px 0 5px; border: 1px solid #767676; border-radius: 50%; background-color: white; box-sizing: border-box"),
291
291
  button: parseDeclarations(`${BUTTON_LIKE}; white-space: pre`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu-browser",
3
- "version": "0.2.4",
3
+ "version": "0.4.0",
4
4
  "description": "Buninu Browser is a browser engine written in JavaScript and WebAssembly for Bun.",
5
5
  "keywords": [
6
6
  "buninu",
@@ -0,0 +1,54 @@
1
+ import {test,expect} from 'bun:test';
2
+ import {writeClipboard} from '../lib/clipboard.js';
3
+ import {RendererHost} from '../lib/renderer/host.js';
4
+
5
+ test('clipboard prefers xclip, preserves exact stdin, and falls back on failure',async()=>{
6
+ const calls=[];let fail=false;
7
+ const options={platform:'linux',which:name=>'/bin/'+name,spawn:(args,opts)=>{calls.push({args,opts});return {exited:Promise.resolve(fail&&args[0].endsWith('xclip')?1:0),kill(){}};}};
8
+ const text='中文\n$(do not execute) `literal`';
9
+ await writeClipboard(text,options);
10
+ expect(calls[0].args).toEqual(['/bin/xclip','-selection','clipboard']);
11
+ expect(calls[0].opts.stdin.toString()).toBe(text);expect(calls).toHaveLength(1);
12
+ fail=true;calls.length=0;await writeClipboard('',options);
13
+ expect(calls.map(c=>c.args[0])).toEqual(['/bin/xclip','/bin/wl-copy']);
14
+ await expect(writeClipboard('x',{...options,which:()=>null})).rejects.toThrow('No working');
15
+ });
16
+
17
+ test('a real button click writes through controller; page-only calls cannot overwrite clipboard',async()=>{
18
+ const copies=[];const host=new RendererHost({clipboardWrite:async text=>{copies.push(text);}});
19
+ try{
20
+ await host.start();await host.call('resize',400,200,1);
21
+ await host.call('loadDocument',{url:'https://clipboard.test/',contentType:'text/html',source:`<body style="margin:0"><button id="b" style="width:200px;height:60px">Copy</button><script>
22
+ window.copyStatus='idle';document.getElementById('b').onclick=async()=>{
23
+ try{await navigator.clipboard.writeText('npx buninu 中文');window.copyStatus='copied';}
24
+ catch(e){window.copyStatus=e.name;}
25
+ };</script>`});
26
+ await host.call('whenLoaded');
27
+ await host.call('evaluate',"document.getElementById('b').click()");await Bun.sleep(30);
28
+ expect(copies).toEqual([]);expect(await host.call('evaluate','window.copyStatus')).toBe('NotAllowedError');
29
+ await host.call('click',30,25);
30
+ for(let i=0;i<40 && !copies.length;i++)await Bun.sleep(25);
31
+ expect(copies).toEqual(['npx buninu 中文']);
32
+ for(let i=0;i<40 && await host.call('evaluate','window.copyStatus')!=='copied';i++)await Bun.sleep(25);
33
+ expect(await host.call('evaluate','window.copyStatus')).toBe('copied');
34
+ }finally{host.close();}
35
+ },20000);
36
+
37
+ test('Windows also uses Bun.which-resolved xclip before its platform fallback',async()=>{
38
+ const seen=[];
39
+ await writeClipboard('Windows 中文',{platform:'win32',which:name=>{seen.push(name);return name==='xclip'?'C:/bin/xclip.exe':null;},spawn:(args,opts)=>{expect(args).toEqual(['C:/bin/xclip.exe','-selection','clipboard']);expect(opts.stdin.toString()).toBe('Windows 中文');return {exited:Promise.resolve(0),kill(){}};}});
40
+ expect(seen).toEqual(['xclip']);
41
+ });
42
+
43
+ test('clipboard read is one-shot, preserves empty/text values, and prefers xclip on Windows too',async()=>{
44
+ const {readClipboard}=await import('../lib/clipboard.js');
45
+ const calls=[];let value='中文\n';
46
+ const options={platform:'win32',which:name=>name==='xclip'?'/bin/xclip':null,spawn:(args)=>{
47
+ calls.push(args);return {stdout:new Response(value).body,exited:Promise.resolve(0),kill(){}};
48
+ }};
49
+ expect(await readClipboard(options)).toBe('中文\n');
50
+ expect(calls).toEqual([['/bin/xclip','-selection','clipboard','-o']]);
51
+ value='';expect(await readClipboard(options)).toBe('');
52
+ await Bun.sleep(30);expect(calls).toHaveLength(2);
53
+ await expect(readClipboard({...options,which:()=>null})).rejects.toThrow('No working');
54
+ });
@@ -0,0 +1,36 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Browser raw frame demo</title>
6
+ <style>body{margin:0;padding:32px;background:#10252f;color:#e4f4fa;font:22px sans-serif}h1{color:#63dedb}a{color:#73d8ff}.box{padding:20px;margin:16px 0;background:#183c4c;border:2px solid #3b7a8d}.track{height:75px;background:#244451;position:relative;overflow:hidden}#bar{position:absolute;left:0;top:0;width:150px;height:75px;background:#49d6c8}.stripe{height:70px;padding:15px;margin-top:10px}</style>
7
+ </head>
8
+ <body>
9
+ <h1>Buninu Browser · Raw BGRA</h1>
10
+ <div class="box">這是 buninu-browser 自己繪製的頁面,不是 Firefox 或 Chromium。<br>Raw 像素經 Casty 傳給 bunterm。請試著捲動、點網址列,或瀏覽其他網站。</div>
11
+ <div class="track">
12
+ <div id="bar">
13
+ </div>
14
+ </div>
15
+ <p id="tick">Animation starting…</p>
16
+ <p>
17
+ <a href="https://example.com">example.com</a> · <a href="https://www.wikipedia.org">Wikipedia</a>
18
+ </p>
19
+ <div class="stripe" style="background:rgb(30,55,75)">Scroll test 1 — 中文 ABC 0123456789</div>
20
+ <div class="stripe" style="background:rgb(35,62,80)">Scroll test 2 — 中文 ABC 0123456789</div>
21
+ <div class="stripe" style="background:rgb(40,69,85)">Scroll test 3 — 中文 ABC 0123456789</div>
22
+ <div class="stripe" style="background:rgb(45,76,90)">Scroll test 4 — 中文 ABC 0123456789</div>
23
+ <div class="stripe" style="background:rgb(50,83,95)">Scroll test 5 — 中文 ABC 0123456789</div>
24
+ <div class="stripe" style="background:rgb(55,90,100)">Scroll test 6 — 中文 ABC 0123456789</div>
25
+ <div class="stripe" style="background:rgb(60,97,105)">Scroll test 7 — 中文 ABC 0123456789</div>
26
+ <div class="stripe" style="background:rgb(65,104,110)">Scroll test 8 — 中文 ABC 0123456789</div>
27
+ <div class="stripe" style="background:rgb(70,111,115)">Scroll test 9 — 中文 ABC 0123456789</div>
28
+ <div class="stripe" style="background:rgb(75,118,120)">Scroll test 10 — 中文 ABC 0123456789</div>
29
+ <div class="stripe" style="background:rgb(80,125,125)">Scroll test 11 — 中文 ABC 0123456789</div>
30
+ <div class="stripe" style="background:rgb(85,132,130)">Scroll test 12 — 中文 ABC 0123456789</div>
31
+ <div class="stripe" style="background:rgb(90,139,135)">Scroll test 13 — 中文 ABC 0123456789</div>
32
+ <div class="stripe" style="background:rgb(95,146,140)">Scroll test 14 — 中文 ABC 0123456789</div>
33
+ <div class="stripe" style="background:rgb(100,153,145)">Scroll test 15 — 中文 ABC 0123456789</div>
34
+ <script>let n=0;setInterval(()=>{n++;document.getElementById('bar').style.left=((n*7)%900)+'px';document.getElementById('tick').textContent='Animation tick: '+n;},50);</script>
35
+ </body>
36
+ </html>
@@ -0,0 +1,36 @@
1
+ # Build context: buninu-browser repository root. Compatible with js-udocker.
2
+ FROM oven/bun:1.4.4
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+ ENV LC_ALL=C.UTF-8
5
+ RUN set -eu; if [ -f /usr/bin/perl ] && [ ! -e /usr/bin/perl5.40.1 ]; then cp /usr/bin/perl /usr/bin/perl5.40.1; fi
6
+ RUN set -eu; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl zstd shared-mime-info util-linux libgcc-s1; apt-get clean
7
+ RUN set -eu; case "$(uname -m)" in aarch64|arm64) arch=aarch64 ;; x86_64) arch=x86_64 ;; *) exit 1 ;; esac; curl -fL --retry 3 "https://pkgs.omarchy.org/stable/$arch/flea-0.3.12-1-$arch.pkg.tar.zst" -o /tmp/flea.pkg.tar.zst; tar --zstd -xf /tmp/flea.pkg.tar.zst -C / usr; /usr/bin/flea --version
8
+ RUN set -eu; mkdir -p /usr/share/omarchy; curl -fL --retry 3 https://codeload.github.com/basecamp/omarchy/tar.gz/3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d -o /tmp/omarchy.tar.gz; tar -xzf /tmp/omarchy.tar.gz --strip-components=1 -C /usr/share/omarchy omarchy-3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d/shell/Commons omarchy-3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d/shell/Ui omarchy-3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d/LICENSE; ln -sfn shell /usr/share/omarchy/qs
9
+ COPY buninu-browser.js /opt/buninu-browser/buninu-browser.js
10
+ COPY frontends /opt/buninu-browser/frontends
11
+ COPY usr /opt/buninu-browser/usr
12
+ COPY lib /opt/buninu-browser/lib
13
+ COPY vendor /opt/buninu-browser/vendor
14
+ COPY LICENSES /opt/buninu-browser/LICENSES
15
+ COPY LICENSE /opt/buninu-browser/LICENSE
16
+ COPY THIRD_PARTY_NOTICES.md /opt/buninu-browser/THIRD_PARTY_NOTICES.md
17
+ COPY package.json /opt/buninu-browser/package.json
18
+ COPY test/fixtures/flea-helpers /usr/local/lib/flea-helpers
19
+ COPY test/fixtures/flea-thumbnail-stub /opt/flea-thumbnail-stub
20
+ COPY test/fixtures/tone.mp3 /root/flea-demo/tone.mp3
21
+ COPY test/fixtures/long-video.mp4 /root/flea-demo/long-video.mp4
22
+ WORKDIR /usr/local/lib/flea-helpers
23
+ RUN set -eu; bun i --backend=copyfile jsmdcui@0.19.2; chmod +x /usr/local/lib/flea-helpers/bin/*; for tool in qs bat glow bwrap; do ln -sfn /usr/local/lib/flea-helpers/bin/$tool /usr/local/bin/$tool; done; install -Dm755 /opt/flea-thumbnail-stub/flea-thumbnail-stub /usr/local/bin/flea-thumbnail-stub; install -Dm644 /opt/flea-thumbnail-stub/flea-placeholder.thumbnailer /usr/share/thumbnailers/flea-placeholder.thumbnailer
24
+ RUN set -eu; install -Dm644 source-mime.xml /usr/share/mime/packages/buninu-source.xml; update-mime-database /usr/share/mime
25
+ RUN set -eu; printf 'const answer = 42;\n' > /root/flea-demo/hello.js; bun /usr/local/lib/flea-helpers/check.mjs
26
+ ENV HOME=/root
27
+ ENV XDG_RUNTIME_DIR=/tmp
28
+ ENV WAYLAND_DISPLAY=wayland-buninu
29
+ ENV XDG_CONFIG_HOME=/root/.config
30
+ ENV XDG_DATA_HOME=/root/.local/share
31
+ ENV XDG_STATE_HOME=/root/.local/state
32
+ ENV XDG_CACHE_HOME=/root/.cache
33
+ ENV QSG_RHI_BACKEND=opengl
34
+ WORKDIR /root
35
+ ENTRYPOINT []
36
+ CMD ["/usr/bin/flea", "--gui", "/root/flea-demo"]
@@ -0,0 +1,43 @@
1
+ #!/usr/local/bin/bun
2
+ // Experimental Flea bat argv adapter, using the unchanged bundled glow highlighter.
3
+ import {RuntimeRegistry} from './node_modules/jsmdcui/src/runtime/registry.js';
4
+ import {Colorscheme} from './node_modules/jsmdcui/src/config/colorscheme.js';
5
+ import {SyntaxDefinition,detectSyntax,loadSyntaxDefinitions} from './node_modules/jsmdcui/src/highlight/parser.js';
6
+ import {Highlighter} from './node_modules/jsmdcui/src/highlight/highlighter.js';
7
+ import {styleToAnsi} from './node_modules/jsmdcui/src/display/ansi-style.js';
8
+ import {fileURLToPath} from 'node:url';
9
+ import {adaptSyntaxRules} from './syntax-compat.mjs';
10
+ let name='',lang='',file=null,first=1,last=Infinity;
11
+ for(const arg of Bun.argv.slice(2)){
12
+ if(['--no-config','--no-custom-assets','--theme=ansi','--color=always','--style=plain','--paging=never','--wrap=never'].includes(arg))continue;
13
+ if(arg.startsWith('--file-name='))name=arg.slice(12);
14
+ else if(arg.startsWith('--language='))lang=arg.slice(11);
15
+ else if(arg.startsWith('--line-range=')){const m=/^--line-range=(\d+):(\d+)$/.exec(arg);if(!m)throw Error('Unsupported line range');first=+m[1];last=+m[2];}
16
+ else if(arg.startsWith('-')&&arg!=='-')throw Error('Unsupported bat option: '+arg);
17
+ else if(file!==null)throw Error('Only one input is supported');else file=arg;
18
+ }
19
+ const text=await (file&&file!=='-'?Bun.file(file):Bun.stdin).text();
20
+ if(Buffer.byteLength(text)>1048576)throw Error('Preview exceeds 1 MiB');
21
+ const runtime=new RuntimeRegistry({repoRoot:fileURLToPath(new URL('./node_modules/jsmdcui',import.meta.url)),configDir:'/tmp'});
22
+ await runtime.init({user:false});
23
+ const scheme=await new Colorscheme(runtime).load('simple'),defs=await loadSyntaxDefinitions(runtime);
24
+ adaptSyntaxRules(defs,SyntaxDefinition);
25
+ const lines=text.replace(/\r\n?/g,'\n').split('\n');if(lines.at(-1)==='')lines.pop();
26
+ const aliases={js:'javascript',jsx:'javascript',mjs:'javascript',cjs:'javascript',node:'javascript',nodejs:'javascript',ts:'typescript',tsx:'typescript',mts:'typescript',cts:'typescript',rs:'rust',py:'python',python3:'python',sh:'shell',bash:'shell',shellscript:'shell',md:'markdown',yml:'yaml',jsonc:'json',json5:'json',jsonl:'json',ndjson:'json',cpp:'c++',cc:'c++',cxx:'c++',hpp:'c++',cs:'csharp','c#':'csharp',rb:'ruby',golang:'go',kt:'kotlin',kts:'kotlin',ps1:'powershell',pwsh:'powershell',bat:'batch',cmd:'batch',docker:'dockerfile',make:'makefile',cmakelists:'cmake',tf:'terraform',proto:'proto',protobuf:'proto',tex:'tex',latex:'tex',diff:'patch',dotenv:'ini'};
27
+ const byType=new Map(defs.map(d=>[d.filetype.toLowerCase(),d]));
28
+ const lookup=key=>byType.get(aliases[key.toLowerCase()]||key.toLowerCase());
29
+ const path=name||file||'',ext=path.split('.').at(-1).toLowerCase();
30
+ const def=lang?lookup(lang):lookup(({jsx:'jsx',mts:'ts',cts:'ts',jsonc:'json',json5:'json',jsonl:'json',ndjson:'json',env:'ini'})[ext]||'')||detectSyntax(defs,{path,firstLine:lines[0]||'',lines:lines.slice(0,50)});
31
+ if(lang&&!def)process.exit(1);
32
+ const highlighter=def?new Highlighter(def,defs):null;let state=null;
33
+ for(let i=0;i<Math.min(lines.length,last);i++){
34
+ const line=lines[i],result=highlighter?.highlightLine(line,state);if(result)state=result.state;
35
+ if(i+1<first)continue;
36
+ if(!result){process.stdout.write(line+'\n');continue;}
37
+ const changes=[...result.changes.entries()];let output='';
38
+ for(let j=0;j<changes.length-1;j++){
39
+ const [from,group]=changes[j],to=changes[j+1][0];
40
+ output+=styleToAnsi(scheme.get(group)).replace(/38;5;(\d+)/g,(_,n)=>String(+n<8?30+(+n):90+(+n)-8))+line.slice(from,to);
41
+ }
42
+ process.stdout.write(output+'\x1b[0m\n');
43
+ }
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ sd=$(dirname "$(realpath "$0")")
3
+ exec bun "$sd/../bat.mjs" "$@"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # Experiment only: no namespace/filesystem isolation; outer prlimit remains.
3
+ envargs=()
4
+ while (($#)); do
5
+ case "$1" in
6
+ --unshare-*|--die-with-parent|--new-session|--clearenv) shift ;; --setenv) envargs+=("$2=$3"); shift 3 ;; --ro-bind|--bind|--symlink) shift 3 ;; --proc|--dev|--tmpfs|--dir) shift 2 ;;
7
+ --json-status-fd) fd=$2; [[ $fd =~ ^[0-9]+$ ]] || exit 2; eval "exec ${fd}>&-"; shift 2 ;; --) shift; break ;; --*) echo "Unsupported experimental bwrap option: $1" >&2; exit 2 ;; *) break ;;
8
+ esac
9
+ done
10
+ exec env "${envargs[@]}" "$@"
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+ # Diagnostic only: retains real bwrap; currently fails under bunproot.
3
+ args=()
4
+ for arg in "$@"; do
5
+ case "$arg" in
6
+ --unshare-all) args+=(--unshare-user --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup-try) ;; --unshare-net) ;; *) args+=("$arg") ;;
7
+ esac
8
+ done
9
+ exec /usr/bin/bwrap-real "${args[@]}"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ sd=$(dirname "$(realpath "$0")")
3
+ exec bun "$sd/../node_modules/jsmdcui/src/index.js" --cat "$@"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ export QML_IMPORT_PATH="/usr/share/omarchy${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}"
3
+ exec bun /opt/buninu-browser/lib/qml/qswayland "$@"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ echo "${0##*/}: unavailable in this experiment" >&2
3
+ exit 1
@@ -0,0 +1,47 @@
1
+ // Exercise the official backend, including bat path validation, sandbox and SGR parsing.
2
+ import cases from './highlight-cases.json' with {type:'json'};
3
+ import {mkdir,realpath} from 'node:fs/promises';
4
+ const base='/root/flea-demo/code';
5
+ await mkdir(base,{recursive:true});
6
+ if(!(await realpath('/usr/local/bin/bat')).startsWith('/usr/'))throw Error('Flea requires bat realpath under /usr');
7
+ const p=Bun.spawn(['/usr/bin/flea','--backend'],{stdin:'pipe',stdout:'pipe',stderr:'pipe'});
8
+ const errors=new Response(p.stderr).text();
9
+ let pending,token=0;
10
+ const reader=(async()=>{
11
+ let buffer='';const decoder=new TextDecoder();
12
+ for await(const chunk of p.stdout){
13
+ buffer+=decoder.decode(chunk,{stream:true});let i;
14
+ while((i=buffer.indexOf('\n'))>=0){
15
+ const line=buffer.slice(0,i);buffer=buffer.slice(i+1);
16
+ const msg=JSON.parse(line);if(msg.t==='code'&&msg.token===token)pending?.(msg);
17
+ }
18
+ }
19
+ })();
20
+ async function ask(params){
21
+ token++;
22
+ let timer;
23
+ try{return await new Promise((resolve,reject)=>{
24
+ pending=resolve;timer=setTimeout(()=>reject(Error('Timed out: '+JSON.stringify(params))),15000);
25
+ p.stdin.write(JSON.stringify({c:'code',token,slot:'verify',first:1,count:400,...params})+'\n');
26
+ });}finally{clearTimeout(timer);pending=null;}
27
+ }
28
+ let checks=0;
29
+ try{
30
+ for(const sample of cases){
31
+ const path=base+'/'+sample.name;await Bun.write(path,sample.text);
32
+ for(const query of [{path},{text:sample.text,name:sample.name,lang:sample.lang}]){
33
+ const result=await ask(query);
34
+ const colored=result.lines?.some(line=>line.some((v,i)=>i%2===0&&v>0));
35
+ if(result.state!=='styled'||!colored)throw Error(`${sample.name} (${query.path?'file':sample.lang}): ${JSON.stringify(result)}`);
36
+ const text=result.lines.map(line=>line.filter((_,i)=>i%2===1).join('')).join('\n');
37
+ if(text!==sample.text.trimEnd())throw Error('Text changed: '+sample.name);
38
+ checks++;
39
+ }
40
+ }
41
+ const plain=await ask({text:'plain text\n',name:'sample.unknown',lang:'not-a-real-language'});
42
+ if(plain.state!=='plain')throw Error('Unknown language must fall back to plain text');
43
+ console.log(`PASS: official Flea colored ${cases.length} formats, ${checks} file/language requests; unknown language falls back to plain`);
44
+ }finally{
45
+ p.stdin.write('{"c":"quit"}\n');p.stdin.end();await p.exited;await reader;
46
+ const stderr=await errors;if(stderr)console.error(stderr);
47
+ }
@@ -0,0 +1,29 @@
1
+ // Real Flea media component, decoder and host PulseAudio connection.
2
+ import {createBrowser} from '/opt/buninu-browser/lib/headless-shell.js';
3
+ import {PulseClient} from '/opt/buninu-browser/lib/audio/pulse-client.js';
4
+ import {mkdtemp,rm} from 'node:fs/promises';
5
+ const dir=await mkdtemp('/tmp/flea-media-check-');
6
+ Object.assign(process.env,{HOME:dir,XDG_CONFIG_HOME:dir+'/config',XDG_CACHE_HOME:dir+'/cache',QML_IMPORT_PATH:'/usr/share/omarchy',FLEA_BIN:'/usr/bin/flea',FLEA_PATH:'/root/flea-demo'});
7
+ let b;
8
+ try{
9
+ const pulse=await Promise.race([PulseClient.connect(),Bun.sleep(5000).then(()=>{throw Error('Pulse connection timeout')})]);
10
+ console.log('Pulse connected');pulse.close();
11
+ b=await createBrowser({spareRenderer:false});
12
+ await b.context.addInitScript(`globalThis.players=[];globalThis.panes=[];let rt;Object.defineProperty(globalThis,'__qmlRuntime',{get:()=>rt,set:r=>{rt=r;const d=r.describe;r.describe=(v,t,...a)=>{if(t.includes('Pane'))panes.push(v);if(t.includes('MediaPlayer'))players.push(v);return d(v,t,...a)}}});`);
13
+ await b.context.navigate('file:///usr/share/flea/ui/boot/shell.qml');
14
+ await Bun.sleep(1500);
15
+ await b.context.press('2',{code:'Digit2',modifiers:2});
16
+ await Bun.sleep(300);
17
+ const point=await b.context.evaluate(`(()=>{const e=[...document.querySelectorAll('[data-qml-type=Text]')].find(e=>e.textContent==='tone.mp3'&&e.getBoundingClientRect().width>0);const r=e.getBoundingClientRect();return [r.x+r.width/2,r.y+r.height/2]})()`);
18
+ await b.context.click(...point);await Bun.sleep(500);
19
+ console.log('before',await b.context.evaluate(`panes.map(p=>({mode:p.viewMode,path:p.previewColumnItem?.path,visible:p.previewColumnItem?.visible,allowed:p.previewColumnItem?.playerAllowed}))`));
20
+ await b.context.evaluate(`panes[0].previewColumnItem.mediaStripItem().toggled()`);
21
+
22
+ for(let i=0;i<8;i++){
23
+ await Bun.sleep(400);
24
+ console.log('column',await b.context.evaluate(`panes.map(p=>({wants:p.previewColumnItem?.wantsPlayback,loaded:p.previewColumnItem?.playerLoaded(),playing:p.previewColumnItem?.mediaPlaying(),position:p.previewColumnItem?.mediaPosition()}))`));
25
+ console.log(await b.context.evaluate(`players.map(p=>({source:p.source,error:p.error,message:p.errorString,position:p.position,duration:p.duration,state:p.playbackState,volume:p.audioOutput?.volume,muted:p.audioOutput?.muted}))`));
26
+ }
27
+ }finally{b?.close();await rm(dir,{recursive:true,force:true});}
28
+
29
+ process.exit(0);
@@ -0,0 +1,27 @@
1
+ // Actual Flea UI + backend, isolated settings, without changing the user's Land.
2
+ import {mkdtemp,rm} from 'node:fs/promises';
3
+ import {createBrowser} from '/opt/buninu-browser/lib/headless-shell.js';
4
+ const dir=await mkdtemp('/tmp/flea-color-ui-');
5
+ Object.assign(process.env,{HOME:dir,XDG_CONFIG_HOME:dir+'/config',XDG_CACHE_HOME:dir+'/cache',XDG_DATA_HOME:dir+'/data',XDG_STATE_HOME:dir+'/state',QML_IMPORT_PATH:'/usr/share/omarchy',FLEA_BIN:'/usr/bin/flea',FLEA_PATH:'/root/flea-demo'});
6
+ let browser;
7
+ try{
8
+ browser=await createBrowser({spareRenderer:false,rendererTimeout:30000});
9
+ await browser.context.resize(900,600);
10
+ await browser.context.addInitScript(`globalThis.codeReaders=[];let rt;Object.defineProperty(globalThis,'__qmlRuntime',{get:()=>rt,set:r=>{rt=r;const d=r.describe;r.describe=(v,t,...a)=>{if(t.includes('CodeLines'))codeReaders.push(v);return d(v,t,...a)}}});`);
11
+ await browser.context.navigate('file:///usr/share/flea/ui/boot/shell.qml');
12
+ async function waitFor(expression){for(let i=0;i<200;i++){if(await browser.context.evaluate(expression))return;await Bun.sleep(50)}throw Error('UI timeout: '+expression);}
13
+ await waitFor(`document.body.innerText.includes('hello.js')`);
14
+ for(const [name,text] of [['sample.jsonl','{"answer":42}\n'],['sample.html','<html><body><h1>Hello</h1></body></html>\n']])await Bun.write('/root/flea-demo/'+name,text);
15
+ for(const name of ['hello.js','sample.jsonl','sample.html']){
16
+ await waitFor(`document.body.innerText.includes(${JSON.stringify(name)})`);
17
+ const point=await browser.context.evaluate(`(()=>{const e=[...document.querySelectorAll('[data-qml-type=Text]')].find(e=>e.textContent===${JSON.stringify(name)}&&e.getBoundingClientRect().width>0);const r=e.getBoundingClientRect();return [r.x+r.width/2,r.y+r.height/2]})()`);
18
+ await browser.context.click(...point);
19
+ await browser.context.press(' ',{code:'Space'});
20
+ if(name.endsWith('.html')){await Bun.sleep(800);await browser.context.press('r',{code:'KeyR'});}
21
+ if(name.endsWith('.html')) await waitFor(`Array.from(document.querySelectorAll('[data-qml-type=Text]')).some(e=>e.textContent.includes('<html>')&&Array.from(e.querySelectorAll('span')).some(s=>s.style.color))`);
22
+ else await waitFor(`codeReaders.some(v=>v.active&&v.path.endsWith(${JSON.stringify(name)})&&v.answer==='styled'&&Object.values(v.held).some(l=>l.some((r,i)=>i%2===0&&r>0)))`);
23
+ await Bun.write('/tmp/flea-js-preview.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));
24
+ console.log('PASS: official Flea UI preview styled: '+name);
25
+ await browser.context.press('Escape',{code:'Escape'});
26
+ }
27
+ }catch(error){await Bun.write('/tmp/flea-html-preview.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));console.error(await browser.context.evaluate(`({text:document.body.innerText,html:Array.from(document.querySelectorAll('[data-qml-type=Text]')).filter(e=>e.textContent.includes('<html>')).map(e=>e.innerHTML),readers:codeReaders.map(v=>({active:v.active,path:v.path,answer:v.answer}))})`));throw error;}finally{browser?.close();await rm(dir,{recursive:true,force:true});}
@@ -0,0 +1,36 @@
1
+ // Build smoke test: actual tools, real Flea backend, no compositor required.
2
+ import {mkdtemp,rm} from 'node:fs/promises';
3
+ const assert=(ok,message)=>{if(!ok)throw Error(message);};
4
+ async function run(args,input='',env={}){
5
+ const p=Bun.spawn(args,{env:{...process.env,PATH:'/usr/local/bin:/usr/bin:/bin',...env},stdin:'pipe',stdout:'pipe',stderr:'pipe'});
6
+ p.stdin.write(input);
7
+ p.stdin.end();
8
+ const [out,err,code]=await Promise.all([new Response(p.stdout).text(),new Response(p.stderr).text(),p.exited]);
9
+ assert(code===0,`${args.join(' ')} exited ${code}: ${err}`);return out;
10
+ }
11
+ assert(Bun.version==='1.4.4',`Expected Bun 1.4.4, got ${Bun.version}`);
12
+ assert(await Bun.file('/usr/share/mime/globs2').exists(),'Missing MIME database');
13
+ assert(await Bun.file('/usr/share/omarchy/qs/Commons/Style.qml').exists(),'Missing Omarchy Commons');
14
+ const ansi=await run(['bat','--color=always','--style=plain','--paging=never','--file-name=sample.js','-'],'const answer = 42;\n');
15
+ assert(ansi.includes('\x1b[')&&Bun.stripANSI(ansi).includes('const answer = 42;'),'bat adapter did not highlight input');
16
+ await run([process.execPath,new URL('./check-highlighting.mjs',import.meta.url).pathname]).then(text=>process.stdout.write(text));
17
+ const cache=await mkdtemp('/tmp/flea-build-check-');
18
+ try{
19
+ const out=await run(['/usr/bin/flea','--backend'],[
20
+ {c:'list',path:'/root/flea-demo',first:20},{c:'thumb',rows:[0,1,2]},{c:'quit'}
21
+ ].map(x=>JSON.stringify(x)+'\n').join(''),{XDG_CACHE_HOME:cache});
22
+ const replies=out.trim().split('\n').map(x=>JSON.parse(x));
23
+ const thumb=replies.find(x=>x.t==='thumbed'&&x.file);
24
+ assert(thumb,'Flea did not accept placeholder thumbnail: '+out);
25
+ assert((await Bun.file(thumb.file).bytes()).subarray(1,4).toString()==='80,78,71','Invalid PNG');
26
+ const {createBrowser}=await import('/opt/buninu-browser/lib/headless-shell.js');
27
+ const page=await createBrowser({spareRenderer:false});
28
+ try {
29
+ const qml=cache+'/check.qml';
30
+ await Bun.write(qml,'import QtQuick\nRectangle {width:320;height:120;color:"white";Text {text:"Flea QML ready";color:"black"}}');
31
+ await page.context.navigate(Bun.pathToFileURL(qml).href);
32
+ assert((await page.context.evaluate('document.body.innerText')).includes('Flea QML ready'),'QML runtime did not render text');
33
+ assert((await page.context.screenshot({format:'png'})).length>100,'QML screenshot is empty');
34
+ } finally {page.close();}
35
+ console.log('PASS: Bun 1.4.4, MIME, Omarchy imports, jsmdcui ANSI and official Flea thumbnail backend, QML rendering');
36
+ }finally{await rm(cache,{recursive:true,force:true});}
@@ -0,0 +1,227 @@
1
+ [
2
+ {
3
+ "name": "sample.js",
4
+ "lang": "js",
5
+ "text": "const answer = 42; // comment\n"
6
+ },
7
+ {
8
+ "name": "sample.mjs",
9
+ "lang": "JavaScript",
10
+ "text": "export const answer = 42;\n"
11
+ },
12
+ {
13
+ "name": "sample.cjs",
14
+ "lang": "node",
15
+ "text": "const answer = require(\"fs\");\n"
16
+ },
17
+ {
18
+ "name": "sample.jsx",
19
+ "lang": "jsx",
20
+ "text": "const answer = 42;\n"
21
+ },
22
+ {
23
+ "name": "sample.ts",
24
+ "lang": "ts",
25
+ "text": "const answer: number = 42;\n"
26
+ },
27
+ {
28
+ "name": "sample.tsx",
29
+ "lang": "tsx",
30
+ "text": "const answer: number = 42;\n"
31
+ },
32
+ {
33
+ "name": "sample.mts",
34
+ "lang": "typescript",
35
+ "text": "export const answer: number = 42;\n"
36
+ },
37
+ {
38
+ "name": "sample.py",
39
+ "lang": "python3",
40
+ "text": "def answer():\n return 42 # comment\n"
41
+ },
42
+ {
43
+ "name": "sample.sh",
44
+ "lang": "bash",
45
+ "text": "#!/bin/sh\necho \"$HOME\" # comment\n"
46
+ },
47
+ {
48
+ "name": "sample.zsh",
49
+ "lang": "zsh",
50
+ "text": "#!/bin/zsh\necho \"$HOME\"\n"
51
+ },
52
+ {
53
+ "name": "sample.fish",
54
+ "lang": "fish",
55
+ "text": "echo \"$HOME\"\n"
56
+ },
57
+ {
58
+ "name": "sample.json",
59
+ "lang": "json",
60
+ "text": "{\"answer\":42,\"ok\":true}\n"
61
+ },
62
+ {
63
+ "name": "sample.jsonc",
64
+ "lang": "jsonc",
65
+ "text": "{\"answer\":42} // comment\n"
66
+ },
67
+ {
68
+ "name": "sample.json5",
69
+ "lang": "json5",
70
+ "text": "{\"answer\":42}\n"
71
+ },
72
+ {
73
+ "name": "sample.jsonl",
74
+ "lang": "jsonl",
75
+ "text": "{\"answer\":42}\n"
76
+ },
77
+ {
78
+ "name": "sample.yaml",
79
+ "lang": "yml",
80
+ "text": "answer: 42 # comment\n"
81
+ },
82
+ {
83
+ "name": "sample.toml",
84
+ "lang": "toml",
85
+ "text": "answer = 42 # comment\n"
86
+ },
87
+ {
88
+ "name": "sample.ini",
89
+ "lang": "ini",
90
+ "text": "[section]\nanswer = 42\n"
91
+ },
92
+ {
93
+ "name": ".env",
94
+ "lang": "dotenv",
95
+ "text": "ANSWER=\"hello\" # comment\n"
96
+ },
97
+ {
98
+ "name": "sample.html",
99
+ "lang": "HTML",
100
+ "text": "<div class=\"hello\">world</div>\n"
101
+ },
102
+ {
103
+ "name": "sample.xml",
104
+ "lang": "xml",
105
+ "text": "<hello answer=\"42\"/>\n"
106
+ },
107
+ {
108
+ "name": "sample.css",
109
+ "lang": "css",
110
+ "text": "body { color: red; margin: 42px; }\n"
111
+ },
112
+ {
113
+ "name": "sample.scss",
114
+ "lang": "css",
115
+ "text": "body { color: red; }\n"
116
+ },
117
+ {
118
+ "name": "sample.sql",
119
+ "lang": "sql",
120
+ "text": "SELECT * FROM users WHERE id = 42;\n"
121
+ },
122
+ {
123
+ "name": "sample.c",
124
+ "lang": "c",
125
+ "text": "int main(void) { return 42; }\n"
126
+ },
127
+ {
128
+ "name": "sample.cpp",
129
+ "lang": "cpp",
130
+ "text": "int main() { return 42; }\n"
131
+ },
132
+ {
133
+ "name": "sample.cs",
134
+ "lang": "c#",
135
+ "text": "public class Hello { int answer = 42; }\n"
136
+ },
137
+ {
138
+ "name": "sample.go",
139
+ "lang": "golang",
140
+ "text": "package main\nfunc main() { println(42) }\n"
141
+ },
142
+ {
143
+ "name": "sample.rs",
144
+ "lang": "rs",
145
+ "text": "fn main() { let answer = 42; }\n"
146
+ },
147
+ {
148
+ "name": "sample.java",
149
+ "lang": "java",
150
+ "text": "public class Hello { int answer = 42; }\n"
151
+ },
152
+ {
153
+ "name": "sample.kt",
154
+ "lang": "kt",
155
+ "text": "val answer = 42\n"
156
+ },
157
+ {
158
+ "name": "sample.rb",
159
+ "lang": "rb",
160
+ "text": "def answer\n return 42\nend\n"
161
+ },
162
+ {
163
+ "name": "sample.php",
164
+ "lang": "php",
165
+ "text": "<?php echo \"hello\"; ?>\n"
166
+ },
167
+ {
168
+ "name": "sample.lua",
169
+ "lang": "lua",
170
+ "text": "local answer = 42 -- comment\n"
171
+ },
172
+ {
173
+ "name": "sample.pl",
174
+ "lang": "perl",
175
+ "text": "my $answer = 42; # comment\n"
176
+ },
177
+ {
178
+ "name": "sample.ps1",
179
+ "lang": "pwsh",
180
+ "text": "$answer = \"hello\" # comment\n"
181
+ },
182
+ {
183
+ "name": "sample.bat",
184
+ "lang": "cmd",
185
+ "text": "@echo off\nset ANSWER=42\nREM comment\n"
186
+ },
187
+ {
188
+ "name": "Dockerfile",
189
+ "lang": "docker",
190
+ "text": "FROM debian:13\nRUN echo hello\n"
191
+ },
192
+ {
193
+ "name": "Makefile",
194
+ "lang": "make",
195
+ "text": "all:\n\techo \"hello\" # comment\n"
196
+ },
197
+ {
198
+ "name": "CMakeLists.txt",
199
+ "lang": "cmake",
200
+ "text": "set(ANSWER 42) # comment\n"
201
+ },
202
+ {
203
+ "name": "sample.md",
204
+ "lang": "md",
205
+ "text": "# Heading\n**bold** and `code`\n"
206
+ },
207
+ {
208
+ "name": "sample.diff",
209
+ "lang": "diff",
210
+ "text": "--- a/file\n+++ b/file\n@@ -1 +1 @@\n-old\n+new\n"
211
+ },
212
+ {
213
+ "name": "sample.tf",
214
+ "lang": "tf",
215
+ "text": "variable \"answer\" { default = 42 }\n"
216
+ },
217
+ {
218
+ "name": "sample.vue",
219
+ "lang": "vue",
220
+ "text": "<template><div class=\"hello\">Hello</div></template>\n"
221
+ },
222
+ {
223
+ "name": "sample.svelte",
224
+ "lang": "svelte",
225
+ "text": "<script>const answer = 42;</script>\n"
226
+ }
227
+ ]