ranger-compiler 3.5.0 → 3.5.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 (338) hide show
  1. package/CHANGELOG.md +1160 -19
  2. package/LICENSE +3 -1
  3. package/README.md +124 -29
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79924 -56922
  7. package/dist/lib/JSON.rgr +102 -91
  8. package/dist/lib/Shell.rgr +4 -4
  9. package/dist/lib/apple/AppleToolchain.rgr +4 -4
  10. package/dist/lib/apple/README.md +5 -4
  11. package/dist/lib/apple/apple_test.rgr +1 -1
  12. package/dist/lib/core/README.md +1 -1
  13. package/dist/lib/evg/EVG.rgr +12 -0
  14. package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
  15. package/dist/lib/evg/EVGA11yTree.rgr +894 -0
  16. package/dist/lib/evg/EVGBox.rgr +267 -0
  17. package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
  18. package/dist/lib/evg/EVGCodepoint.rgr +316 -0
  19. package/dist/lib/evg/EVGColor.rgr +700 -0
  20. package/dist/lib/evg/EVGCommands.rgr +177 -0
  21. package/dist/lib/evg/EVGComponent.rgr +331 -0
  22. package/dist/lib/evg/EVGComponentTest.rgr +387 -0
  23. package/dist/lib/evg/EVGConnector.rgr +541 -0
  24. package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
  25. package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
  26. package/dist/lib/evg/EVGEasing.rgr +370 -0
  27. package/dist/lib/evg/EVGEffectTest.rgr +253 -0
  28. package/dist/lib/evg/EVGElement.rgr +4914 -0
  29. package/dist/lib/evg/EVGFixedTest.rgr +289 -0
  30. package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
  31. package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
  32. package/dist/lib/evg/EVGFling.rgr +244 -0
  33. package/dist/lib/evg/EVGFocus.rgr +400 -0
  34. package/dist/lib/evg/EVGFocusTest.rgr +399 -0
  35. package/dist/lib/evg/EVGGradient.rgr +322 -0
  36. package/dist/lib/evg/EVGGrapheme.rgr +190 -0
  37. package/dist/lib/evg/EVGGrid.rgr +975 -0
  38. package/dist/lib/evg/EVGHitTest.rgr +208 -0
  39. package/dist/lib/evg/EVGHoles.rgr +294 -0
  40. package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
  41. package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
  42. package/dist/lib/evg/EVGHostTree.rgr +834 -0
  43. package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
  44. package/dist/lib/evg/EVGImageDecode.rgr +117 -0
  45. package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
  46. package/dist/lib/evg/EVGInspect.rgr +883 -0
  47. package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
  48. package/dist/lib/evg/EVGJsonTest.rgr +535 -0
  49. package/dist/lib/evg/EVGLayout.rgr +4220 -0
  50. package/dist/lib/evg/EVGMeasure.rgr +1267 -0
  51. package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
  52. package/dist/lib/evg/EVGPatch.rgr +1721 -0
  53. package/dist/lib/evg/EVGPatchTest.rgr +692 -0
  54. package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
  55. package/dist/lib/evg/EVGReconcile.rgr +226 -0
  56. package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
  57. package/dist/lib/evg/EVGReject.rgr +126 -0
  58. package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
  59. package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
  60. package/dist/lib/evg/EVGRuler.rgr +232 -0
  61. package/dist/lib/evg/EVGRulerTest.rgr +172 -0
  62. package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
  63. package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
  64. package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
  65. package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
  66. package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
  67. package/dist/lib/evg/EVGText.rgr +105 -0
  68. package/dist/lib/evg/EVGTextEngine.rgr +536 -0
  69. package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
  70. package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
  71. package/dist/lib/evg/EVGToolbar.rgr +1369 -0
  72. package/dist/lib/evg/EVGTransition.rgr +728 -0
  73. package/dist/lib/evg/EVGTreeJson.rgr +721 -0
  74. package/dist/lib/evg/EVGUnit.rgr +554 -0
  75. package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
  76. package/dist/lib/evg/EvgApp.rgr +189 -0
  77. package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
  78. package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
  79. package/dist/lib/evg/EvgHost.rgr +451 -0
  80. package/dist/lib/evg/EvgTest.rgr +91 -0
  81. package/dist/lib/evg/EvgTraceColor.rgr +236 -0
  82. package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
  83. package/dist/lib/evg/EvgTraceFit.rgr +767 -0
  84. package/dist/lib/evg/EvgTracePath.rgr +435 -0
  85. package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
  86. package/dist/lib/evg/EvgViewport.rgr +313 -0
  87. package/dist/lib/evg/FxDemoDoc.rgr +185 -0
  88. package/dist/lib/evg/HOSTS.md +227 -0
  89. package/dist/lib/evg/ISSUES.md +835 -0
  90. package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
  91. package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
  92. package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
  93. package/dist/lib/evg/PLAN_EVG.md +518 -0
  94. package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
  95. package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
  96. package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
  97. package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
  98. package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
  99. package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
  100. package/dist/lib/evg/PathBuilder.rgr +333 -0
  101. package/dist/lib/evg/README.md +1236 -0
  102. package/dist/lib/evg/SPEC.md +1252 -0
  103. package/dist/lib/evg/SVGPathParser.rgr +1199 -0
  104. package/dist/lib/evg/SvgParser.rgr +1736 -0
  105. package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
  106. package/dist/lib/evg/VectorShapes.rgr +331 -0
  107. package/dist/lib/evg/VectorStroke.rgr +107 -0
  108. package/dist/lib/evg/VectorViewBox.rgr +380 -0
  109. package/dist/lib/evg/agent/README.md +290 -0
  110. package/dist/lib/evg/agent/evg_agent.rgr +899 -0
  111. package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
  112. package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
  113. package/dist/lib/evg/agent/fixtures/connector.css +63 -0
  114. package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
  115. package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
  116. package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
  117. package/dist/lib/evg/agent/fixtures/popover.css +99 -0
  118. package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
  119. package/dist/lib/evg/agent/roundtrip.sh +74 -0
  120. package/dist/lib/evg/agent/smoke.sh +230 -0
  121. package/dist/lib/evg/android/README.md +97 -0
  122. package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
  123. package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
  124. package/dist/lib/evg/android/androidstubs/App.kt +30 -0
  125. package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
  126. package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
  127. package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
  128. package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
  129. package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
  130. package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
  131. package/dist/lib/evg/android/androidstubs/View.kt +113 -0
  132. package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
  133. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
  134. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
  135. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
  136. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
  137. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
  138. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
  139. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
  140. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
  141. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
  142. package/dist/lib/evg/apple/README.md +128 -0
  143. package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
  144. package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
  145. package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
  146. package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
  147. package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
  148. package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
  149. package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
  150. package/dist/lib/evg/bench/README.md +64 -0
  151. package/dist/lib/evg/bench/layout-bench.mjs +316 -0
  152. package/dist/lib/evg/bench/layout-cases.mjs +510 -0
  153. package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
  154. package/dist/lib/evg/bin/.gitignore +17 -0
  155. package/dist/lib/evg/evg_test.rgr +313 -0
  156. package/dist/lib/evg/gl/README.md +198 -0
  157. package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
  158. package/dist/lib/evg/gl/blur-check.mjs +399 -0
  159. package/dist/lib/evg/gl/boxmodel.json +1 -0
  160. package/dist/lib/evg/gl/demo.html +46 -0
  161. package/dist/lib/evg/gl/effect-presets.css +285 -0
  162. package/dist/lib/evg/gl/effect-presets.js +63 -0
  163. package/dist/lib/evg/gl/effect-shots.mjs +135 -0
  164. package/dist/lib/evg/gl/evg-a11y.js +566 -0
  165. package/dist/lib/evg/gl/evg-binary.js +160 -0
  166. package/dist/lib/evg/gl/evg-engine.js +296 -0
  167. package/dist/lib/evg/gl/evg-fx.js +237 -0
  168. package/dist/lib/evg/gl/evg-gestures.js +209 -0
  169. package/dist/lib/evg/gl/evg-list.js +167 -0
  170. package/dist/lib/evg/gl/evg-measure.js +186 -0
  171. package/dist/lib/evg/gl/evg-textinput.js +303 -0
  172. package/dist/lib/evg/gl/evg-view.js +144 -0
  173. package/dist/lib/evg/gl/evg-webgl.js +3942 -0
  174. package/dist/lib/evg/gl/fx-check.mjs +998 -0
  175. package/dist/lib/evg/gl/fx-demo.html +166 -0
  176. package/dist/lib/evg/gl/fx-demo.js +2 -0
  177. package/dist/lib/evg/gl/fx-serve.mjs +47 -0
  178. package/dist/lib/evg/gl/gestures-check.mjs +189 -0
  179. package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
  180. package/dist/lib/evg/gl/measure-check.mjs +135 -0
  181. package/dist/lib/evg/gl/rotation-check.mjs +207 -0
  182. package/dist/lib/evg/gl/shift-check.mjs +113 -0
  183. package/dist/lib/evg/gl/stroke-check.mjs +168 -0
  184. package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
  185. package/dist/lib/evg/gl/view-check.mjs +343 -0
  186. package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
  187. package/dist/lib/evg/html/evg-dom.js +318 -0
  188. package/dist/lib/evg/html/evg-html.js +600 -0
  189. package/dist/lib/evg/inspect/README.md +415 -0
  190. package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
  191. package/dist/lib/evg/inspect/evg-inspect.js +947 -0
  192. package/dist/lib/evg/inspect/shots/css.png +0 -0
  193. package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
  194. package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
  195. package/dist/lib/evg/inspect/shots/state.png +0 -0
  196. package/dist/lib/evg/inspect/shots.mjs +226 -0
  197. package/dist/lib/evg/oracle/css-blur.json +576 -0
  198. package/dist/lib/evg/oracle/css-box.json +157 -0
  199. package/dist/lib/evg/oracle/css-timing.json +709 -0
  200. package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
  201. package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
  202. package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
  203. package/dist/lib/evg/original/EVGColor.clj +310 -0
  204. package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
  205. package/dist/lib/evg/original/SVGPath.rgr +627 -0
  206. package/dist/lib/evg/original/Vec2.crgr +103 -0
  207. package/dist/lib/evg/ranger.json +9 -0
  208. package/dist/lib/evg/showcase/README.md +246 -0
  209. package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
  210. package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
  211. package/dist/lib/evg/showcase/build.mjs +651 -0
  212. package/dist/lib/evg/showcase/pages/album.tsx +30 -0
  213. package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
  214. package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
  215. package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
  216. package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
  217. package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
  218. package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
  219. package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
  220. package/dist/lib/evg/showcase/pages/more.tsx +332 -0
  221. package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
  222. package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
  223. package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
  224. package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
  225. package/dist/lib/evg/showcase/pages/units.tsx +35 -0
  226. package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
  227. package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
  228. package/dist/lib/evg/showcase/pages/views.tsx +223 -0
  229. package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
  230. package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
  231. package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
  232. package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
  233. package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
  234. package/dist/lib/evg/showcase/themes/more-default.css +128 -0
  235. package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
  236. package/dist/lib/evg/showcase/themes/showcase.css +508 -0
  237. package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
  238. package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
  239. package/dist/lib/evg/showcase/themes/views-default.css +15 -0
  240. package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
  241. package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
  242. package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
  243. package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
  244. package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
  245. package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
  246. package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
  247. package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
  248. package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
  249. package/dist/lib/evg/web/responsive/README.md +97 -0
  250. package/dist/lib/evg/web/responsive/build.mjs +90 -0
  251. package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
  252. package/dist/lib/evg/web/responsive/index.html +177 -0
  253. package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
  254. package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
  255. package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
  256. package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
  257. package/dist/lib/evg/web/tools/minify.mjs +60 -0
  258. package/dist/lib/evg/web/tracer/build.mjs +87 -0
  259. package/dist/lib/evg/web/tracer/index.html +2805 -0
  260. package/dist/lib/evg/web/tracer/sample.png +0 -0
  261. package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
  262. package/dist/lib/evgr/Cargo.lock +21 -0
  263. package/dist/lib/evgr/Cargo.toml +19 -0
  264. package/dist/lib/evgr/README.md +140 -0
  265. package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
  266. package/dist/lib/evgr/bench/compare.mjs +252 -0
  267. package/dist/lib/evgr/bench/speed.mjs +277 -0
  268. package/dist/lib/evgr/bin/.gitignore +3 -0
  269. package/dist/lib/evgr/src/bin/bench.rs +106 -0
  270. package/dist/lib/evgr/src/bin/smoke.rs +13 -0
  271. package/dist/lib/evgr/src/grid.rs +730 -0
  272. package/dist/lib/evgr/src/lib.rs +1003 -0
  273. package/dist/lib/evgr/src/style.rs +468 -0
  274. package/dist/lib/evgr/src/text.rs +83 -0
  275. package/dist/lib/image/BitReader.rgr +171 -0
  276. package/dist/lib/image/Buffer.rgr +173 -0
  277. package/dist/lib/image/DCT.rgr +283 -0
  278. package/dist/lib/image/Deflate.rgr +339 -0
  279. package/dist/lib/image/HuffmanDecoder.rgr +181 -0
  280. package/dist/lib/image/ImageBuffer.rgr +706 -0
  281. package/dist/lib/image/JPEGDecoder.rgr +808 -0
  282. package/dist/lib/image/PNGDecoder.rgr +544 -0
  283. package/dist/lib/image/PNGEncoder.rgr +388 -0
  284. package/dist/lib/image/PPMImage.rgr +224 -0
  285. package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
  286. package/dist/lib/image/README.md +33 -0
  287. package/dist/lib/image/RasterBuffer.rgr +294 -0
  288. package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
  289. package/dist/lib/image/WebPDecoder.rgr +433 -0
  290. package/dist/lib/image/WebPLossless.rgr +1035 -0
  291. package/dist/lib/image/WebPLossy.rgr +1929 -0
  292. package/dist/lib/image/ranger.json +9 -0
  293. package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
  294. package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
  295. package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
  296. package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
  297. package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
  298. package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
  299. package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
  300. package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
  301. package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
  302. package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
  303. package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
  304. package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
  305. package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
  306. package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
  307. package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
  308. package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
  309. package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
  310. package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
  311. package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
  312. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
  313. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
  314. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
  315. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
  316. package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
  317. package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
  318. package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
  319. package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
  320. package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
  321. package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
  322. package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
  323. package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
  324. package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
  325. package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
  326. package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
  327. package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
  328. package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
  329. package/dist/lib/image/tests/run_webp_tests.sh +39 -0
  330. package/dist/lib/rust/RsJson.rgr +468 -0
  331. package/dist/lib/rust/RsPrelude.rgr +1663 -0
  332. package/dist/lib/shell_test.rgr +7 -7
  333. package/dist/lib/stdlib.rgr +21 -5
  334. package/dist/lib/zip/ranger.json +6 -0
  335. package/dist/rgrc.js +87318 -67610
  336. package/package.json +1010 -1040
  337. package/dist/README.md +0 -117
  338. package/dist/package.json +0 -47
@@ -0,0 +1,788 @@
1
+ # EVG Inspector — devtools for a picture nobody can read
2
+
3
+ **Status:** phases 1, 2 and 3 are built, and phase 4 turned out to be a
4
+ different feature — see [`inspect/README.md`](inspect/README.md).
5
+ The rest of this file is still design.
6
+ **License:** AGPL-3.0-or-later (Gallery).
7
+
8
+ > **What exists now.** `EVGInspect.rgr` (the walk, node paths, the box model,
9
+ > the computed style, the cascade), `EVGStyleSheet.planRules` and
10
+ > `EVGStyleSheet.reload`, `inspect/evg-inspect.js` (the panel, the overlay and
11
+ > a CSS editor), attribution on `EVGDrawCmd` behind a flag, and two hosts wired
12
+ > to it: the `gallery/ui` dashboard on the WebGL painter and the PPTX slide
13
+ > editor on the SVG one. Twenty gates in `inspect/inspect-check.mjs`, one
14
+ > browser gate in `inspect/browser-smoke.mjs`, and one end-to-end gate in
15
+ > `inspect/live-css-check.mjs`.
16
+ >
17
+ > Since then: the adapter is asynchronous throughout, so the transports that
18
+ > cannot answer on the same tick are open rather than a rewrite away; states
19
+ > can be held on (`EVGInspectForce`, keyed by path so it survives the rebuild
20
+ > a tree-literal page does on every input); and the panel can write the sheet
21
+ > back to disk, which the watch then sees like any other save.
22
+ >
23
+ > **§7 is superseded and the reason is worth keeping.** That section designs an
24
+ > override layer — a table of (path, property, value) re-applied after every
25
+ > cascade — because an edit written onto an element dies on the next rebuild.
26
+ > It works, and it is unnecessary: the element tree is an app's **output**, but
27
+ > the stylesheet is its **input**, and handing back a changed input needs no
28
+ > interception at all. The app re-parses and re-cascades exactly as it did at
29
+ > `init`, everything downstream follows because it always did, and the text in
30
+ > the editor is the text that goes in the file rather than something to
31
+ > translate. What the override layer was for — surviving a rebuild — is free,
32
+ > because the tree is rebuilt *from* the thing that was edited.
33
+ >
34
+ > The pieces of §7 that survive are the ones about what an edit cannot reach:
35
+ > a value the app writes onto the element, and structure. Both are now shown in
36
+ > the panel rather than fought.
37
+
38
+ An EVG app in a browser is one `<canvas>` element. Open the browser's dev
39
+ tools on it and you get exactly that: one element, no children, no styles, no
40
+ box model. The DOM painter is not much better — `lib/evg/html/evg-html.js`
41
+ emits a flat pile of `<rect>` and `<path>` in paint order, with no names, no
42
+ nesting and no relation to the tree that produced them. You can see the
43
+ picture. You cannot ask it anything.
44
+
45
+ This is the design for asking. A panel that shows the element hierarchy, the
46
+ box model, the computed style with the rules that set it, and whatever a
47
+ component chose to say about itself — over a live app on either painter, or
48
+ over a frame captured from one and opened later with no app running at all.
49
+
50
+ ---
51
+
52
+ ## 1. What already comes out of an EVG app
53
+
54
+ Three channels, and every browser host in this repository uses all three.
55
+ `gallery/ui/demo/main.js` says it in its own header:
56
+
57
+ ```
58
+ displayListJson() what to draw
59
+ hitId(x, y) what is under the pointer
60
+ a11yJson() what it MEANS
61
+ ```
62
+
63
+ They share one property that is the whole reason this design is short: **each
64
+ is derived from the same laid-out element tree, on the same pass.** Nothing is
65
+ written twice. `EVGA11yFromTree` walks the tree the picture came from and says
66
+ so in its header — an app that describes its tree a second time has two
67
+ descriptions to keep in step, and the one nobody can see is the one that rots.
68
+
69
+ The inspector is the fourth channel and obeys the same rule:
70
+
71
+ ```
72
+ EVGElement tree (laid out)
73
+ │
74
+ ┌────────────┬───────┴───────┬──────────────┐
75
+ ▼ ▼ ▼ ▼
76
+ EVGDisplayList EVGHitTest EVGA11yFromTree EVGInspect
77
+ what to draw what is what it means WHAT IT IS
78
+ under here (this file)
79
+ ```
80
+
81
+ `EVGInspect` is a new module beside `EVGA11yFromTree`. It is a walk, not a
82
+ renderer, and it produces JSON. What it does **not** do is give the display
83
+ list a second opinion about geometry: every rectangle it reports is the one
84
+ `EVGLayout` computed and `EVGDisplayList` drew, read off the same fields.
85
+
86
+ ### Why not just read the DOM on the SVG painter
87
+
88
+ Because then the inspector only works on one painter, only in a browser, and
89
+ only for what SVG happens to express. The point of the display-list seam
90
+ (`gl/README.md`) is that WebGL, SVG, SDL+GL, PDF, PNG and the Android/iOS
91
+ ports are the same picture. An inspector attached to one painter's output
92
+ inherits none of that. Attached to the tree, it works on all of them, and on
93
+ the native targets it works where a browser's dev tools cannot reach at all.
94
+
95
+ ---
96
+
97
+ ## 2. Node identity
98
+
99
+ Everything else depends on being able to name a node.
100
+
101
+ `EVGElement.id` is not it. It is optional, it is the app's own test id, it is
102
+ frequently `""`, and nothing enforces uniqueness — `hitId` returning `""` for
103
+ most of a page is normal today.
104
+
105
+ The identity is a **path**, assigned by the inspect walk and by nothing else:
106
+
107
+ ```
108
+ "0" the root
109
+ "0/3" its fourth child
110
+ "0/3/1" that child's second child
111
+ "0/3/k:share/1" a child with `key` set uses the key instead of the index
112
+ ```
113
+
114
+ Index segments where there is no key, the key where there is one. This is the
115
+ same trick `EVGComponentHost` already plays with `enter` / `leave` / `pathFor`,
116
+ for the same reason: a name that is unique among siblings composes into a name
117
+ that is unique in the tree, and no registry is needed to hand them out.
118
+
119
+ What the path costs is honest and worth stating: it is **structural**. Insert a
120
+ row above the selected one and `0/3/1` now names a different element. Keyed
121
+ children are immune, which is exactly the set of children that a list rebuild
122
+ reorders — so in practice the selection survives the rebuilds that matter and
123
+ breaks on the ones where "the same node" has no meaning anyway. The panel
124
+ handles the break the way it has to: if a path stops resolving, the selection
125
+ is dropped and said to be dropped, never silently re-pointed at whatever is
126
+ now at that index.
127
+
128
+ ---
129
+
130
+ ## 3. Attribution: which commands did this element produce
131
+
132
+ Hovering a node in the panel has to light up the pixels it drew, and clicking
133
+ the canvas has to select the node under the pointer. The second is a hit test
134
+ and already exists. The first is not derivable from anything today: a draw
135
+ command carries geometry and colour and no idea where it came from.
136
+
137
+ So `EVGDrawCmd` gains one field:
138
+
139
+ ```ranger
140
+ def node:string "" ; inspect path of the element that emitted this, or ""
141
+ ```
142
+
143
+ emitted in `toJson` as `"n"` (a key the format does not use) and **only when
144
+ attribution is switched on**:
145
+
146
+ ```ranger
147
+ def attribute:boolean false ; on EVGDisplayList
148
+ ```
149
+
150
+ Off, the JSON is byte-identical to today's — which is a gate, not a hope; see
151
+ §10. On, a list grows about twelve bytes per command and every painter ignores
152
+ the extra key it does not read.
153
+
154
+ The binary bridge gets the same treatment: `EVGSceneBinary` already pools
155
+ strings and refers to them by index, so a node id is **one more int per
156
+ command** in the record, pointing into the pool it already carries. The stride
157
+ changes; the shape does not.
158
+
159
+ This is worth more than the highlight it was added for. "Which element drew
160
+ these three commands" is the question behind the class of bug that
161
+ `gl/README.md` describes as five painters each deciding again what a box means
162
+ — border-radius working in PDF and silently not in PNG, because one painter
163
+ read `box.borderRadius` and another a stale `el.borderRadius`. With
164
+ attribution, the command that is wrong names the element that is wrong.
165
+
166
+ ---
167
+
168
+ ## 4. What a node carries
169
+
170
+ Two responses, because the tree is fetched whole and the detail is fetched for
171
+ one node at a time. A dashboard is 1 200 elements; sending every computed
172
+ property for every one of them is megabytes nobody will look at.
173
+
174
+ ### 4.1 The tree
175
+
176
+ Flat array, parent by id, with the same key names and the same `gen` field
177
+ `EVGA11yTree.toJson` already writes — short keys, defaults omitted, one less
178
+ format for a host to learn.
179
+
180
+ ```json
181
+ {
182
+ "gen": 12,
183
+ "root": "0",
184
+ "w": 1240, "h": 560,
185
+ "nodes": [
186
+ {
187
+ "id": "0/3/k:share",
188
+ "p": "0/3",
189
+ "tag": "div",
190
+ "tid": "row-Share",
191
+ "cls": "menu-row menu-row-sub",
192
+ "comp": "menubar/menu:File/row:share",
193
+ "role": "menuitem",
194
+ "text": "Share",
195
+ "box": [220, 148, 180, 28],
196
+ "in": [232, 152, 156, 20],
197
+ "m": [0, 0, 2, 0],
198
+ "b": [1, 1, 1, 1],
199
+ "pd": [4, 12, 4, 12],
200
+ "flags": ["hover", "overlay"],
201
+ "cmds": [41, 42, 43],
202
+ "kids": 2
203
+ }
204
+ ]
205
+ }
206
+ ```
207
+
208
+ * `tid` is the element's own `EVGElement.id` — the app's test id, when it set
209
+ one. It is not the identity; `id` is. Keeping both is the point: the panel
210
+ can name a node the way the app names it, and `hitId` keeps meaning what
211
+ it means today.
212
+ * `box` is the border box — `calculatedX/Y/Width/Height`, the rectangle the
213
+ display list drew and the hit test tests.
214
+ * `in` is the content box — `calculatedInnerWidth/Height` and the padded
215
+ origin. Those two plus `m` / `b` / `pd` are the four rings of the box-model
216
+ diagram, and they come from `EVGBox` resolved to pixels, not from the
217
+ authored units. The authored units are in the style detail, where the
218
+ difference between `padding: 1em` and `12px` belongs.
219
+ * `comp` is `EVGComponentHost.pathFor` when a component built this subtree,
220
+ `""` otherwise. It is what turns "some div" into "the row component of the
221
+ File menu", and it is free: the host already keeps the path.
222
+ * `flags` is the small set of booleans that change how a node behaves rather
223
+ than how it looks: `abs`, `overlay`, `clip`, `inline`, `hover`, `focus`,
224
+ `pressed`, `hidden`. The state flags matter because a `:hover` rule that
225
+ won is unreadable without knowing the element is hovered.
226
+ * `cmds` is present only when the list was built with attribution on.
227
+
228
+ `text` is the element's own text, truncated. A node is not a text dump.
229
+
230
+ ### 4.2 The detail
231
+
232
+ ```json
233
+ {
234
+ "id": "0/3/k:share",
235
+ "computed": {
236
+ "background-color": "#2f2f33",
237
+ "padding-left": "12px",
238
+ "font-size": "13px",
239
+ "width": "180px"
240
+ },
241
+ "cascade": [
242
+ { "sel": "(override)", "decls": [ {"p":"background-color","v":"#ff0000","win":true} ] },
243
+ { "sel": "(inline)", "decls": [ {"p":"width","v":"180","win":true} ] },
244
+ { "sel": ".menu-row:hover", "state": "hover", "on": true, "theme": "dark",
245
+ "decls": [ {"p":"background-color","v":"#2f2f33","win":false} ] },
246
+ { "sel": ".menu-row", "media": "(min-width: 900px)",
247
+ "decls": [ {"p":"background-color","v":"#1c1c1f","win":false},
248
+ {"p":"padding-left","v":"12px","win":true} ] }
249
+ ],
250
+ "units": { "padding-left": "1em -> 12px", "width": "180 -> 180px" },
251
+ "debug": [
252
+ { "src": "MenuCtl", "rows": [
253
+ {"k":"pendingMs","v":"100","t":"ms"},
254
+ {"k":"openPath","v":"File","t":"enum"},
255
+ {"k":"owner","v":"0/3","t":"ref"} ] }
256
+ ]
257
+ }
258
+ ```
259
+
260
+ `cascade` is in winning order, strongest first, and every declaration says
261
+ whether it won. That is the devtools view: the struck-through rules are the
262
+ ones with `"win": false`, and you can see at a glance that a hover rule is
263
+ sitting on top of the base one. `units` is the second half of the same
264
+ question — a value the sheet wrote as `1em` and the layout resolved to `12px`,
265
+ which is where `EVGUnit` bugs are visible and nowhere else.
266
+
267
+ ---
268
+
269
+ ## 5. Where the cascade provenance comes from
270
+
271
+ This is the only part of the design that is not a walk over existing data, so
272
+ it gets its own section.
273
+
274
+ `EVGStyleSheet` writes values into element fields and keeps no record of which
275
+ rule wrote them. Worse for a naive approach, the fast path does not even look
276
+ at rules at run time: `buildPlan` flattens every matching rule for a
277
+ (class, theme, state) key into two parallel arrays of names and values, caches
278
+ it by key, and `applyGroup` replays the flattened list. By the time a value
279
+ reaches an element, the rule is long gone, and reconstructing it afterwards by
280
+ re-matching selectors would be a second implementation of the cascade — the
281
+ exact kind of second opinion this design exists to avoid.
282
+
283
+ **Record it where the rule is in hand: in `buildPlan`.**
284
+
285
+ ```ranger
286
+ def planRules:[int] ; parallel to planNames/planValues: index into `rules`,
287
+ ; or -1 for a state-clear's initial value
288
+ ```
289
+
290
+ One int per planned declaration, pushed in `planGroup` and `planStateClears`
291
+ beside the name and value they already push. Then:
292
+
293
+ * the cost is **per plan, not per element**. A 1 600-row table has a handful of
294
+ plans and a hundred thousand applications of them; a design that traced at
295
+ apply time would pay a hundred thousand times for the same answer.
296
+ * the order is already the cascade order, because `buildPlan` calls
297
+ `planGroup` in the four passes that *are* the precedence — plain, themed,
298
+ stateful, themed-and-stateful — and the last write wins. So "which
299
+ declaration won" is not computed, it is read: the last entry for a property
300
+ in the plan is the winner and the earlier ones are the overridden list. No
301
+ specificity is recalculated anywhere, which means the panel cannot disagree
302
+ with the engine about who won.
303
+ * it costs nothing when the inspector is off except the array itself, and it
304
+ can be skipped entirely behind `def traceRules:boolean false` if even that
305
+ is too much — but the array is small and always-on keeps one code path.
306
+
307
+ Two provenances are not in the plan and are added by the walk:
308
+
309
+ * **inline** — `EVGElement.inlineProps` already records exactly which
310
+ properties the authoring layer set directly, because `applyDecls` needs it to
311
+ know what not to overwrite. It is the "author wrote this on the element"
312
+ channel, already there.
313
+ * **override** — the inspector's own edits, §7.
314
+
315
+ `applyToDirect` / `applyDecls`, the non-planned path, keeps the rule in hand
316
+ and can record it directly.
317
+
318
+ ### What a rule can say about itself
319
+
320
+ `EVGStyleRule` today knows its `className`, `pseudo`, `theme`, `media` and
321
+ source `order`. That is enough to print `.menu-row:hover` and the media
322
+ condition, which is what the `sel` field above is: **reconstructed, not
323
+ stored**. It is not enough to jump to the line in the CSS file that wrote it.
324
+
325
+ Adding `def sourceLine:int 0` to `EVGStyleRule`, set by `addRulesIn` from the
326
+ offset it already has, is a dozen lines and turns the panel's rule header into
327
+ a link. It is phase 6 and not a prerequisite for anything.
328
+
329
+ ---
330
+
331
+ ## 6. Components sharing debug info
332
+
333
+ The picture explains the *what*. A component knows the *why*, and today it has
334
+ nowhere to say it: `MenuCtl.pendingTid` and the 100 ms submenu timer beside it
335
+ are invisible in every one of the four channels, and they are the state that
336
+ explains a submenu that will not close.
337
+
338
+ ### The format
339
+
340
+ ```ranger
341
+ class EVGDebugNote {
342
+ def group:string "" ; who is speaking — usually the class name
343
+ def label:string "" ; the field
344
+ def value:string "" ; already a string; formatting is the emitter's job
345
+ def kind:string "" ; how to render it, see below
346
+ }
347
+ ```
348
+
349
+ `kind` is one of `text`, `num`, `px`, `ms`, `bool`, `enum`, `color`, `ref`.
350
+ Only two of them do anything beyond formatting:
351
+
352
+ * `color` gets a swatch,
353
+ * `ref` is **another node's inspect path**, and the panel makes it clickable.
354
+ That is what makes "this controller owns that element" navigable, and it is
355
+ the reason `kind` exists at all rather than everything being text.
356
+
357
+ ### The sink
358
+
359
+ Notes are not a field on `EVGElement` — a 10 000-row grid should not carry an
360
+ empty array per row for a feature that is off. They live in one process-wide
361
+ sink, and an element points into it:
362
+
363
+ ```ranger
364
+ def debugSlot:int (0 - 1) ; on EVGElement: head of its note chain, or -1
365
+ ```
366
+
367
+ ```ranger
368
+ class EVGDebug {
369
+ sfn enabled:boolean () ; one static flag
370
+ sfn beginPass:void () ; clears the sink, like EVGComponentHost
371
+ sfn note:void (el:EVGElement group:string label:string value:string kind:string)
372
+ }
373
+ ```
374
+
375
+ `note` returns immediately when the flag is off, so the cost in a shipping
376
+ build is one boolean test at each call site and one int on each element. When
377
+ it is on, it pushes onto parallel arrays and links the note into the element's
378
+ chain, so the inspect walk joins notes to nodes in one pass with no lookup.
379
+
380
+ `beginPass` is called where `EVGComponentHost.beginPass` already is. Notes are
381
+ per frame, like everything else here; a note that survived a rebuild would be
382
+ describing a tree that no longer exists.
383
+
384
+ ### The convention for a UI library
385
+
386
+ `gallery/ui` is the first user and sets the rule, because a debug channel with
387
+ no rule becomes a second log:
388
+
389
+ 1. **One group per controller instance**, named for the class.
390
+ 2. **A row is a reason, never a restatement.** `width: 180` is already in the
391
+ box model and must not be a note. `pendingMs: 100` is not anywhere else and
392
+ must be.
393
+ 3. **The a11y trace is not duplicated.** Role, name, expanded, pressed,
394
+ checked, selected, disabled are the twelve fields `npm run ui:report`
395
+ already diffs against Radix, and they are in the a11y channel. A note that
396
+ repeats one of them is two sources for one fact.
397
+ 4. **`ref` to the element the controller owns**, always. That single row is
398
+ what connects a controller to the picture.
399
+
400
+ So `MenuCtl` emits `openPath`, `pendingTid`, `pendingMs`, `keyboardMode`, and
401
+ a `ref` to its surface. `ToggleCtl` emits almost nothing, which is correct.
402
+
403
+ ---
404
+
405
+ ## 7. Editing
406
+
407
+ Read-only would already pay for itself, but the question the panel is opened
408
+ with is usually "what if this were 20px", and answering it by editing a `.rgr`
409
+ file and rebuilding is the loop the panel exists to shorten.
410
+
411
+ An edit is an **override**: a (path, property, value) triple in a table the
412
+ inspector owns.
413
+
414
+ ```json
415
+ {"op": "set", "id": "0/3/k:share", "prop": "padding-left", "value": "24px", "scope": "sticky"}
416
+ ```
417
+
418
+ Two scopes, and the difference is what happens on the next frame:
419
+
420
+ * **`once`** — write the value onto the element now and re-lay-out. It is gone
421
+ the moment the cascade runs again, which on a tree-literal app is the next
422
+ keystroke. Right for looking.
423
+ * **`sticky`** — keep it in the override table, and reapply the whole table
424
+ **after** `applyTree` on every frame, in the walk `EVGInspect` is doing
425
+ anyway. Right for working.
426
+
427
+ Sticky overrides are keyed on the inspect path, which is why they survive a
428
+ rebuild that the elements themselves do not: `gallery/ui`'s demos discard the
429
+ whole tree on every input and build a new one, and an edit written onto an
430
+ element would last one frame. An edit written against `0/3/k:share` lands on
431
+ whatever element that path names next time, which is the node the user was
432
+ looking at.
433
+
434
+ The override layer is also the export. "Copy as CSS" walks the table and
435
+ prints rules against each node's class list — a starting point for the sheet
436
+ edit the user is going to make anyway, not a claim to have made it.
437
+
438
+ Clearing is `{"op":"clear"}` for one node or all, and the panel shows the
439
+ count, because an override table you have forgotten about is a debugging
440
+ session that ends in confusion.
441
+
442
+ **What cannot be edited.** Anything the element does not own: text content
443
+ produced by a component, a value a controller writes every frame (it wins the
444
+ next frame, and the panel says so rather than fighting it), and structure. No
445
+ node insertion, no deletion, no reparenting. Those change what the app *is*,
446
+ and the app is the source of truth for that.
447
+
448
+ ---
449
+
450
+ ## 8. The overlay
451
+
452
+ The highlight is **not** in the display list. Putting it there would pollute
453
+ every screenshot, every parity run and every PDF taken while the panel is
454
+ open, and it would have to be implemented once per painter.
455
+
456
+ Instead `evg-inspect-overlay.js` positions plain DOM over the canvas: four
457
+ nested absolutely-positioned boxes for margin, border, padding and content,
458
+ in the same four colours a browser uses because there is no reason to invent
459
+ different ones, plus a label with the tag, class and pixel size. The
460
+ positioning arithmetic is the same scale-and-offset that `evg-a11y.js` already
461
+ does to put mirrored nodes at painted rectangles — one implementation, moved
462
+ into a shared helper.
463
+
464
+ It therefore works identically over the WebGL canvas and over the SVG painter's
465
+ `<svg>`, and it works on the offline bundle viewer where there is no app at
466
+ all. Native targets get the same overlay drawn by the port; on those, the
467
+ alternative — pushing overlay quads into the display list — is available and
468
+ acceptable, because a native screenshot harness is not running while somebody
469
+ has an inspector open.
470
+
471
+ ---
472
+
473
+ ## 9. Where the panel runs
474
+
475
+ Three modes, one panel, one protocol.
476
+
477
+ ```
478
+ ┌ in-page ────────────────────────────────────────────────┐
479
+ │ ?inspect=1 panel is DOM beside the canvas │
480
+ │ direct calls, no serialisation, no server │
481
+ └──────────────────────────────────────────────────────────┘
482
+ ┌ attached ───────────────────────────────────────────────┐
483
+ │ panel in one page, app in another / on a device │
484
+ │ JSON over WebSocket on the preview server's /inspect │
485
+ │ the only mode that reaches SDL, Android, iOS │
486
+ └──────────────────────────────────────────────────────────┘
487
+ ┌ offline ────────────────────────────────────────────────┐
488
+ │ a .evginspect bundle, opened with no app running │
489
+ │ read-only: tree, styles, debug notes, the frame itself │
490
+ └──────────────────────────────────────────────────────────┘
491
+ ```
492
+
493
+ **In-page** is the default and needs no infrastructure: `main.js` already holds
494
+ the app object and calls `displayListJson()` on it. The panel is a fourth
495
+ consumer of the same object.
496
+
497
+ **Attached** exists because the seam is portable and the panel should not have
498
+ to be. `EVGInspect` compiles to the same targets `EVGDisplayList` does — that
499
+ is the entire reason the display list has the shape it has — so a Ranger app
500
+ on a Raspberry Pi or an Android phone can answer the same four ops over a
501
+ socket, and the panel in a laptop browser cannot tell the difference. This is
502
+ the capability a browser's dev tools structurally cannot have.
503
+
504
+ **Offline** is what the user asked for as "just inspect the renderer's output".
505
+ A bundle is one JSON file:
506
+
507
+ ```json
508
+ { "evginspect": 1,
509
+ "frame": { "...": "the display list, with attribution" },
510
+ "tree": { "...": "the inspect tree" },
511
+ "a11y": { "...": "the a11y tree" },
512
+ "styles": { "...": "per-node cascade for every node" },
513
+ "png": "data:image/png;base64,..." }
514
+ ```
515
+
516
+ written by `npm run evg:inspect -- page.tsx out.evginspect` beside the existing
517
+ `npm run evg:displaylist`. It is a debugging artifact you can attach to a bug
518
+ report, and — the reason it will earn its keep — **a CI artifact.** When a
519
+ parity or screenshot gate fails, the run attaches the bundle for the failing
520
+ frame, and the person reading the failure a day later gets the tree, the
521
+ styles and the commands instead of two PNGs and a percentage.
522
+
523
+ ### The protocol
524
+
525
+ Four ops in, JSON out, one version field, no streaming.
526
+
527
+ ```
528
+ → {"op":"tree", "gen":12}
529
+ → {"op":"node", "id":"0/3/k:share", "want":["style","debug","units"]}
530
+ → {"op":"hit", "x":220, "y":140} ← {"id":"0/3/k:share"}
531
+ → {"op":"frame", "attribute":true} ← the display list
532
+ → {"op":"set" | "clear", ...} ← §7
533
+ ```
534
+
535
+ `gen` is a frame counter the app already bumps. The panel sends the generation
536
+ it last saw; a response with a different `gen` means the tree moved under it,
537
+ and the panel refetches rather than merging. There is no incremental tree
538
+ update in this design and there should not be one until a measurement asks for
539
+ it: a full tree of a 1 200-element dashboard is roughly 200 KB of JSON, built
540
+ by a walk that costs less than the layout that preceded it.
541
+
542
+ ---
543
+
544
+ ## 10. What it costs when it is off
545
+
546
+ Stated as a list because this is the part that gets checked, not assumed:
547
+
548
+ | Piece | Off | On |
549
+ | --- | --- | --- |
550
+ | `EVGDrawCmd.node` | one empty string per command; not written to JSON | ~12 bytes per command of JSON |
551
+ | `EVGSceneBinary` | stride unchanged | one int per command, into the existing string pool |
552
+ | `planRules` | one int per *planned declaration*, never per element | read by the walk |
553
+ | `EVGElement.debugSlot` | one int per element, never touched | a chain head |
554
+ | `EVGDebug.note` | one boolean test per call site | pushes onto the sink |
555
+ | `EVGInspect` walk | never runs | one tree walk per request, not per frame |
556
+ | Overlay, panel | not loaded | DOM over the canvas |
557
+
558
+ The line that has to hold: **`displayListJson()` with attribution off produces
559
+ the same bytes it produces today.** That is a test, not a claim — §11.
560
+
561
+ ---
562
+
563
+ ## 11. How it is checked
564
+
565
+ The repository's habit is that a second implementation of anything is
566
+ differenced against the first — `pptx:html:parity` renders every scene through
567
+ both painters and compares area, because two screenshots side by side is how a
568
+ claim gets believed and not how it gets checked. The inspector is a second
569
+ description of the same tree and gets the same treatment.
570
+
571
+ 1. **Attribution gate.** Every command in an attributed list carries a node id
572
+ that resolves in the inspect tree, and the command's rectangle lies within
573
+ that node's border box. Two known exceptions, asserted as exceptions rather
574
+ than allowed silently: a shadow extends past the box by its blur and offset,
575
+ and a text run may overhang by the font's side bearings. Anything else
576
+ escaping its box is a real bug and this is the first thing that would find
577
+ it.
578
+ 2. **Cascade gate.** For every node and every property in `computed`, the
579
+ winning entry in `cascade` has the same value. This is the test that keeps
580
+ the trace honest: it fails the moment a write path stops going through the
581
+ plan, which is exactly the drift the trace is vulnerable to.
582
+ 3. **Box-model gate.** `box`, `in`, `m`, `b`, `pd` are mutually consistent —
583
+ content box plus padding plus border equals border box — on every node of
584
+ every showcase page. Cheap, and it checks `EVGBox` resolution as a
585
+ side effect.
586
+ 4. **Painter agnosticism.** The panel is driven over the same frame through
587
+ the WebGL host and the SVG host; the tree and the detail must be identical,
588
+ because neither painter is consulted to produce them.
589
+ 5. **Off-cost gate.** `displayListJson()` byte-compared with attribution off,
590
+ before and after the change, over the showcase pages and the pptx deck.
591
+ 6. **Debug-note lint.** Notes are checked against §6's rules the way
592
+ `EVGA11yTree.lint` checks the a11y tree: a group that restates a box-model
593
+ field, or a `ref` that does not resolve, is reported. An unchecked debug
594
+ channel becomes a log within a month.
595
+
596
+ The headless runs go in beside the existing ones: `npm run evg:inspect:test`.
597
+
598
+ ---
599
+
600
+ ## 12. The runner: the same channels, with no browser
601
+
602
+ The four reads this design is built on — tree, style, hit, frame — are not
603
+ only what a panel needs. They are what a **test** needs, and an EVG app can be
604
+ driven through them in-process, with no browser, no page and no protocol.
605
+ That makes the headless runner a use of the inspector rather than a separate
606
+ project, and it is measured here rather than asserted:
607
+
608
+ ```bash
609
+ npm run ui:runner:bench # gallery/ui/bench/runner-vs-browser.mjs
610
+ ```
611
+
612
+ Twenty tests, each a fresh instance or page, five interactions, five
613
+ assertions. The browser half drives Chromium over raw CDP against a page
614
+ holding one canvas and a ready flag — **no Playwright and no application**, so
615
+ every browser number below is generous to the browser.
616
+
617
+ ```
618
+ target start-up per test suite total
619
+ ------------------------------------------------------------------
620
+ EVG runner / MessageDemo 9.2 ms 4.2 ms 93 ms
621
+ EVG runner / DashboardDemo 22.8 ms 107.1 ms 2 165 ms
622
+ Chromium floor (no app) 251.0 ms 91.9 ms 2 090 ms
623
+
624
+ per call
625
+ browser evaluate 1.0–1.4 ms · DOM query 2.4–3.4 ms
626
+ click 2.2–3.1 ms · screenshot 35–43 ms
627
+ MessageDemo boot 2.4 ms · frame 0.34 ms · a11y 0.14 ms · hit 0.04 ms
628
+ DashboardDemo boot 31 ms · frame 9.1 ms · a11y 8.8 ms · hit 11 ms
629
+ hit (cached) 0.007 ms
630
+ ```
631
+
632
+ Four things are worth reading off that table, and the last one is the reason
633
+ this section is in the inspector's design and not in a benchmark README.
634
+
635
+ **The browser's per-test cost is a constant.** Roughly 90 ms of page creation,
636
+ navigation and protocol round trips before the app under test has done
637
+ anything, on an empty page. Nothing in a suite tunes it away, and a real
638
+ Playwright test adds its driver process, its selector engine, its actionability
639
+ polling and the app's own boot on top.
640
+
641
+ **An assertion is thirty to a hundred times cheaper in process.** 1.0–3.4 ms
642
+ for an `evaluate` or a DOM query, against 0.04–0.14 ms to read the hit test or
643
+ the accessible tree directly. A suite whose cost is dominated by assertions
644
+ rather than by page loads is where this compounds hardest.
645
+
646
+ **The runner has no screenshot problem.** A `Page.captureScreenshot` is 35–43 ms
647
+ and produces pixels that then have to be diffed with a tolerance. The display
648
+ list is already in hand, costs what the frame costs, and compares as
649
+ structure — a command that moved says which command moved. Pixels stay the
650
+ right tool for the two painters, and only for them (§11.4).
651
+
652
+ **The runner's per-test cost is your app's frame, and that is the finding.**
653
+ `MessageDemo` is 22× faster than the floor. `DashboardDemo` is *slower* than
654
+ it, and not because of the method: `hitId` costs 11 ms and `hitIdCached`
655
+ costs 0.007 ms, because the first re-renders the entire page before testing a
656
+ point and the second tests the layout that is already there. `a11yJson` does
657
+ the same rebuild. So a test that interacts and then makes three assertions
658
+ pays for four full frames when it needed one — a 1 400× difference on one of
659
+ them, sitting in an app that looks fine.
660
+
661
+ That is precisely the class of thing this design exists to make visible, and
662
+ it is an argument for building the panel first and the runner second: the
663
+ runner's speed is the app's frame cost, and the frame cost is what the
664
+ inspector shows you. A frame panel over `EVGStyleSheet`'s existing
665
+ `planHits` / `planMisses` and the layout counters is the natural next step
666
+ after the phase 2 in §13, and it is what turns "the dashboard suite is slow" into
667
+ "the dashboard rebuilds its table three times per assertion".
668
+
669
+ ### What this is not
670
+
671
+ The repository already runs this split and names the two halves correctly:
672
+ `gallery/ui/conformance/oracle/*_oracle.mjs` drives **real Radix and Base UI in
673
+ a real browser** and writes a trace to JSON; `*_check.mjs` replays the same
674
+ questions against the Ranger controllers with no browser at all. The browser is
675
+ the **oracle**, run when the reference might have changed. The headless run is
676
+ the **gate**, run on every commit. `README.md` in `gallery/ui` says the browser
677
+ playground is "a lead, not the gate", and that sentence is the whole policy.
678
+
679
+ So the runner does not replace the browser. It replaces the *majority* of
680
+ tests that never needed one, and leaves the browser the ones that do:
681
+
682
+ * the painters — WebGL and SVG produce pixels and only a browser has them
683
+ (`pptx:html:parity` already differences the two);
684
+ * real font rasterisation and platform text shaping;
685
+ * the input the platform owns — IME composition, clipboard, the text-input
686
+ bridge driven through the DevTools protocol in `PLAN_INPUTS.md`;
687
+ * whatever a screen reader is actually handed, as opposed to what the a11y
688
+ tree claims.
689
+
690
+ ### A mocked backend costs nothing here
691
+
692
+ The reason e2e suites reach for a browser is usually not the browser. It is
693
+ that the app only assembles inside one. An EVG app under this runner is an
694
+ ordinary object in the test's own process, so a mock is an argument, not an
695
+ interception: no route table, no service worker, no port, no fixture server,
696
+ and no async at all if the mock is synchronous. `gallery/ui`'s checks already
697
+ construct the demo, hand it CSS and press it by id.
698
+
699
+ ### Determinism, which may be worth more than the speed
700
+
701
+ There is nothing to wait for. No auto-wait, no retry, no polling for an
702
+ element to become actionable, no timeout to tune, and no frame budget to race.
703
+ An interaction returns when the frame is built, and the assertion reads that
704
+ frame. The flake class that makes browser suites expensive to own is not
705
+ reduced here, it is absent — and a suite that never flakes is one nobody has
706
+ to re-run, which is a second multiplier on top of the first.
707
+
708
+ ## 13. Phases
709
+
710
+ Each phase is useful on its own; none of them requires the next.
711
+
712
+ | # | What | Roughly |
713
+ | --- | --- | --- |
714
+ | 1 | ✅ **built** — `EVGInspect` walk, node paths, tree + box model. In-page panel, overlay, hit-to-select. Read-only, both painters. | the spine |
715
+ | 2 | ✅ **built** — `planRules`, the cascade view, `units`. Gates 2 and 3. | the reason it is devtools and not a tree dump |
716
+ | 3 | ✅ **built** (except the binary bridge) — attribution on `EVGDrawCmd`, command list per node, gates 1 and 5. | |
717
+ | 4 | ↷ **replaced** — CSS as a live input (disk → watch → SSE → the app's own cascade) instead of an override layer. See the note at the top. | the loop this exists to shorten |
718
+ | 5 | `EVGDebug` sink and note format; adopted across `gallery/ui` controllers; note lint. | |
719
+ | 6 | Offline bundle + `npm run evg:inspect`, CI attachment on gate failure. | the highest value per line of the six |
720
+ | 7 | Attached transport over the preview server's `/inspect`; SDL, Android and iOS answering the same ops. | |
721
+ | 8 | `EVGStyleRule.sourceLine`, and a source span from `JSXToEVG`, so a rule and a node both link to the line that wrote them. | |
722
+
723
+ Phase 6 is placed after 5 rather than last on purpose: a bundle is worth more
724
+ than a live panel to the person reading a CI failure tomorrow, and it needs
725
+ nothing from phase 7.
726
+
727
+ The runner of §12 is not a phase here, because it needs nothing from this file
728
+ that does not already exist — `gallery/ui`'s checks drive apps headless today.
729
+ What it needs is the frame panel that phase 2 makes possible: the runner's
730
+ speed is the app's frame cost, and until that is visible, a suite that is
731
+ slower than a browser looks like a verdict on the method. Redundant rebuilds
732
+ behind `hitId` and `a11yJson` are worth fixing on their own account and are
733
+ independent of everything above.
734
+
735
+ ---
736
+
737
+ ## 14. Non-goals
738
+
739
+ * **Not a profiler.** Frame timing is a real want and a different panel.
740
+ `EVGStyleSheet` already counts `planHits` / `planMisses`, and `EVGDisplayList`
741
+ has the numbers quoted in its own header — enough for that panel to be built
742
+ later, on the same transport, without this one growing a timeline.
743
+ * **Not a time-travel debugger.** No frame history, no stepping backwards.
744
+ The offline bundle is one frame and says so.
745
+ * **Not structural editing.** §7.
746
+ * **Not a replacement for the a11y mirror.** They answer different questions
747
+ and the inspector reads the a11y tree rather than recomputing it — which
748
+ incidentally makes `EVGA11yTree.lint`'s findings visible in a panel for the
749
+ first time.
750
+ * **Not in the shipping bundle by default.** Everything here is behind a flag,
751
+ and the flag defaults to off.
752
+
753
+ ---
754
+
755
+ ## 15. Open questions
756
+
757
+ * **Node paths across a keyed reorder.** `EVGReconcile` already decides which
758
+ nodes are the same node across a rebuild. Should the inspect path be derived
759
+ from the reconciler's identity instead of from structure, where a reconciler
760
+ is in use? It would make selection survive more rebuilds. It would also make
761
+ the path mean two different things depending on the app, and the panel would
762
+ have no way to tell. Leaning towards structure-only, and letting keys carry
763
+ the weight.
764
+ * **How much of the cascade to send.** The design sends the whole matched set
765
+ per node on demand. A node matched by forty rules in a large sheet makes that
766
+ response large. Truncating to the rules that touch a property with a winner
767
+ is easy and loses the "this rule matched but set nothing you asked about"
768
+ case, which is occasionally the answer.
769
+ * **Overrides and the plan cache.** A sticky override applied after
770
+ `applyTree` does not invalidate the layout signature the plan cache compares.
771
+ If an override touches a layout property, the cache must be told, or the
772
+ frame keeps the old geometry. The mechanism exists (`planLayoutSig`); the
773
+ wiring is phase 4's real work and the place a bug would hide.
774
+
775
+ ---
776
+
777
+ ## 16. Summary
778
+
779
+ The picture an EVG app draws already knows everything the panel needs. The
780
+ tree was laid out, the boxes were resolved, the rules were matched and a
781
+ component knew why it was doing what it did — and then all of it was thrown
782
+ away and only quads came out the other end.
783
+
784
+ This design keeps four of those things and throws away nothing else: a path
785
+ per node, a rule index per planned declaration, a node id per draw command,
786
+ and a note chain per element that opted in. Everything the panel shows is read
787
+ from what the engine decided, never recomputed beside it, which is the only
788
+ property that keeps an inspector true a year after it is written.