ranger-compiler 3.5.1 → 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 +1064 -20
  2. package/LICENSE +3 -1
  3. package/README.md +123 -28
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79923 -56921
  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,671 @@
1
+ # Native hosts — a spike
2
+
3
+ How far EVG can move from "a native 2D API paints our list" towards "the
4
+ platform's own presentation tree shows our document", on the three platforms
5
+ that have a painter today: the browser, Apple (UIKit / SwiftUI) and Android
6
+ (View / Compose). What the platform can genuinely do better than a canvas,
7
+ where the wall is, and what to try first.
8
+
9
+ Status: **S0–S3 built** (§8) — the platform measurers; the engine off the UI
10
+ thread on all three platforms; `EVGHostTree`, the diff channel; and the
11
+ retained DOM painter on it, on the responsive page and on RealTrainer — both
12
+ live as `?painter=dom` at
13
+ [/evg/responsive/](https://terotests.github.io/Ranger/evg/responsive/?painter=dom)
14
+ and [/realtrainer/](https://terotests.github.io/Ranger/realtrainer/?painter=dom).
15
+ S4–S6 are design. The numbers
16
+ in it are measured from the repository as it is (`npm run rt:bench 40`, the
17
+ stylesheet tallies in §3.3), not projected.
18
+
19
+ Related: [`README.md`](README.md) (the pipeline and the seam),
20
+ [`gl/README.md`](gl/README.md) (the display list as a GPU input),
21
+ [`html/evg-html.js`](html/evg-html.js) (the SVG painter and its header on
22
+ identity), [`PLAN_ACCESSIBILITY.md`](PLAN_ACCESSIBILITY.md) (the second list),
23
+ [`inspect/README.md`](inspect/README.md) (the path identity),
24
+ [`apple/README.md`](apple/README.md), [`android/README.md`](android/README.md),
25
+ [`../../gallery/ui/PLAN_INPUTS.md`](../../gallery/ui/PLAN_INPUTS.md) (the text-input bridge).
26
+
27
+ ---
28
+
29
+ ## 0. The short version
30
+
31
+ Three levels of "native", and the middle one is the one to build:
32
+
33
+ | | What the platform gets | What EVG keeps | Print parity |
34
+ | --- | --- | --- | --- |
35
+ | **L0 — painter** (today) | a flat list of quads, runs, paths, clips | everything: cascade, layout, lines, hit test, a11y | exact |
36
+ | **L1 — native nodes, EVG geometry** | one real node per element (a `div`, a `View`, a composable), placed at the rectangle EVG computed | cascade, layout, line breaks, hit test, a11y *content* | exact, same lines |
37
+ | **L2 — native layout** | the platform lays the boxes out itself (CSS flex/grid, SwiftUI stacks, Compose Row/Column) | the tree, the stylesheet, the controllers | broken unless test-gated |
38
+
39
+ The claim the engine makes everywhere — *the same tree, styled by the same
40
+ sheet, comes out the same on every target* — is exactly what L2 gives up.
41
+ Every platform lays flex out slightly differently (§3.3, §4.3, §5.3 list the
42
+ deltas), and none of them can be asked to lay a page out the way the PDF was.
43
+ So L2 is an **opt-in mode behind a conformance oracle**, not the default, and
44
+ only the web has an oracle for it today (`PLAN_CSS_LAYOUT_AND_FONTS.md` §4.1
45
+ rule 9: the box model and 20 grid fixtures are already checked against
46
+ Chromium).
47
+
48
+ L1 is where the platform's real advantages live and none of the parity is
49
+ lost: element identity that survives a frame, the platform's text-input
50
+ session, its scrolling and compositor, its accessibility tree *as the tree*
51
+ rather than a mirror, its animations, and — on every platform — the engine
52
+ running **off the UI thread**. What L1 needs from EVG is one new thing, and it
53
+ is the same thing on all three platforms: a derived channel that says *what
54
+ changed*, keyed by an identity that survives a rebuild (§6, D2).
55
+
56
+ **Does this fight the engine's design?** Only at L2. The design has two
57
+ halves — *one layout, so every target agrees*, and *a dumb seam, so a painter
58
+ is small* — and L1 keeps the first and adds a second seam beside the second:
59
+ a host tree next to the display list, derived on the same pass by the same
60
+ rule the a11y tree and the inspector already follow. A painter stays a
61
+ painter. What L1 gives up is a slogan, not an invariant: the display list is
62
+ no longer the *only* way out of the engine. L2 is the one that trades the
63
+ first half away, which is why it is a mode and not a default.
64
+
65
+ The order to try it in is **web first** (the platform already has everything
66
+ L1 needs and the parity harness exists), **Compose second** (its `Layout`
67
+ composable is the cleanest fit any of the three has for "EVG measures, the
68
+ platform hosts"), **SwiftUI third** (the same fit through the `Layout`
69
+ protocol, but with a view-count ceiling that decides which apps qualify).
70
+
71
+ ---
72
+
73
+ ## 1. Where things stand
74
+
75
+ The pipeline is `tree → cascade → layout → display list → painter`, and the
76
+ seam below the display list is load-bearing (`README.md`, "The display list").
77
+ The three platforms in question are all painters:
78
+
79
+ | Platform | Host | Painter | Per frame |
80
+ | --- | --- | --- | --- |
81
+ | Browser | `<canvas>` + WebGL 2 (`gl/evg-webgl.js`) or `<svg>` (`html/evg-html.js`) | instanced quads / SVG nodes rebuilt | list → GPU buffers, or list → new SVG tree |
82
+ | Apple | `UIView` (`ui/ios`, `realtrainer/ios`), SwiftUI `Canvas` (watch — SwiftUI is the only toolkit there, §7.5) | `EvgPainter.swift` over `CGContext` | every command re-rasterised |
83
+ | Android | `View` (`ui/android`, `realtrainer/android`) | `EvgPainter.kt` over `android.graphics.Canvas` | every command re-rasterised |
84
+
85
+ What the engine already does that an L1 host would lean on:
86
+
87
+ * **Retention.** `EVGReconcile` keeps element objects across a rebuild by
88
+ `key`; the style cache reports `layoutClean()` / `nothingChanged()`; a
89
+ scroll container's clip is a **layer** whose commands are moved in place
90
+ (`EVGDisplayList.refreshLayers`); a `keepLayout` subtree is a **fragment**
91
+ whose commands are reused, not rebuilt.
92
+ * **Identity that exists but is not on the seam.** `EVGElement.key`
93
+ (sibling-scoped), `EVGInspect`'s structural path (`0/3/k:share`),
94
+ `inspectSlot` on the element and `EVGDrawCmd.node` on the command — the
95
+ last two only while the inspector is attached.
96
+ * **Four derived lists from one walk:** the display list, the hit test, the
97
+ a11y tree, the inspector tree. The rule they share ("derived from the
98
+ laid-out tree, same pass, recomputes nothing") is the rule a fifth list
99
+ would follow.
100
+ * **The platform already owns text input on the web.**
101
+ `gl/evg-textinput.js` parks a real `<input>` over the drawn field and
102
+ Ranger mirrors it. RealTrainer's Android port commits through an
103
+ `InputConnection`; its iOS port becomes first responder and types through
104
+ the same bridge.
105
+ * **A binary scene format built for a thread hop.** `toBinary()` writes an
106
+ `EVGSceneBinary` whose `cmds`, `pts` and `ends` are `int_buffer`s — an
107
+ `Int32Array` on the JavaScript target, and therefore transferable to and
108
+ from a Worker with no copy. Its header records why: 12 ms of layout was
109
+ followed by 62 ms of `toJson` and 19 ms of `JSON.parse` on a 10 084-command
110
+ slide.
111
+
112
+ And what a scroll frame costs on the engine side, on the app that is most
113
+ like a phone app (`npm run rt:bench 40`: a 40× diary, 390×844, 200 scroll
114
+ frames):
115
+
116
+ ```
117
+ scrollDocument 0.00 ms
118
+ + display() 0.04 ms
119
+ + displayListJson() 0.34 ms
120
+ + JSON.parse 0.64 ms
121
+ hitId() (per pointer move) 0.03 ms
122
+ a11yJson() + parse (settled only) 3.96 ms
123
+ 177 draw commands, 24185 bytes
124
+ ```
125
+
126
+ The engine's share of a scroll frame is well under a millisecond. Whatever a
127
+ scroll frame costs on a device today, it is being spent **below the seam** —
128
+ in the painter re-rasterising 177 commands, and on the browser in the DOM
129
+ mirror the a11y tree is rebuilt into once the page settles. That is the
130
+ budget an L1 host is competing for, and it is why "native" here means
131
+ *hosting*, not a faster layout.
132
+
133
+ ---
134
+
135
+ ## 2. The three levels, and what decides between them
136
+
137
+ **L0 — the painter.** What exists. The platform contributes glyph
138
+ rasterisation, a 2D API and nothing else. Text is a picture; a screen reader
139
+ gets a mirror; a text field is a proxy; the compositor never sees a layer.
140
+
141
+ **L1 — native nodes, EVG geometry.** One host node per element, or per kept
142
+ subtree, placed at `calculatedX/Y/Width/Height`. The host does what a canvas
143
+ cannot:
144
+
145
+ * keep a node's **identity** — so a CSS transition, a focus ring, a caret,
146
+ a `UITextField` or a `BasicTextField` has something to attach to;
147
+ * run **text input** in the field itself rather than over it;
148
+ * **scroll and animate on the compositor** — a scroll layer becomes a
149
+ `transform: translate`, a `CALayer` position, a `graphicsLayer`
150
+ translation, and the UI thread is not in the loop;
151
+ * publish **accessibility as the tree itself**: a `<button>` is a button,
152
+ `Modifier.semantics` is the a11y node, `.accessibilityLabel` is the name —
153
+ and the separate mirror (3.96 ms a frame, on settle) goes away on the web;
154
+ * let the engine run **off the UI thread**, because what crosses to the UI
155
+ thread is a diff of nodes and rectangles, not a call into the engine.
156
+
157
+ Geometry stays EVG's, so the hit test, the a11y rectangles, the inspector and
158
+ the PDF still agree with the picture. The one thing that moves is **who
159
+ measures text** (D3): the platform's own metrics, so that the glyphs the host
160
+ draws are the glyphs EVG wrapped with — but the *breaks* stay EVG's.
161
+
162
+ **L2 — native layout.** Emit `display: flex`, an `HStack`, a `Row`, and let
163
+ the platform place the children. This is what the question in the title is
164
+ really asking, and the honest answer is that it works for the common subset
165
+ on each platform and stops at a different place on each:
166
+
167
+ * CSS is the only one of the three that has EVG's model *natively* — flex
168
+ with grow/shrink/basis, wrap, grid with `fr` and `minmax` and areas.
169
+ The mapping is small (§3.3) and the deltas are known and testable.
170
+ * SwiftUI has stacks and `Grid`, no `flex-shrink`, no `flex-basis`, no
171
+ wrapping `HStack`, and a `Layout` protocol for everything else — which is
172
+ where EVG's own algorithm would go (§4.3).
173
+ * Compose has `Row`/`Column` with `weight`, `FlowRow`, no CSS grid, and a
174
+ `Layout` composable that is the cleanest custom-layout API of the three —
175
+ and, again, EVG's algorithm is what would go in it (§5.3).
176
+
177
+ So on the two mobile platforms "native layout" collapses into "EVG's layout
178
+ running inside the platform's layout protocol", which is L1 with extra steps.
179
+ On the web it is a real option — and it is the option that breaks print
180
+ parity, because a browser's line breaking is not `EVGTextEngine`'s and its
181
+ sub-pixel flex arithmetic is not `EVGLayout`'s (ISSUES #8 exists because the
182
+ two were compared). L2 on the web is therefore a **mode**: the page becomes a
183
+ DOM application whose hit testing is DOM events and whose a11y is the DOM,
184
+ driven by Ranger controllers, and it is checked against the existing
185
+ Chromium oracles rather than trusted.
186
+
187
+ ---
188
+
189
+ ## 3. Web
190
+
191
+ ### 3.1 What exists
192
+
193
+ * `html/evg-html.js` — the display list as `<svg>`. Rebuilt every frame; the
194
+ header says why it cannot do otherwise: *"a draw command carries no id … so
195
+ every frame rebuilds every node, and a CSS transition, a focus ring or a
196
+ native `<input>` has nothing stable to attach to. That is a property of
197
+ the SEAM, not of this file."* Differenced against WebGL at 0.022 % on the
198
+ feature sheet, 0.000 % on the pptx deck.
199
+ * `EVGHTMLRenderer.rgr` — the print preview: absolutely positioned `<div>`s
200
+ from `calculatedX/Y`, one per element, with real `@font-face`s. Not
201
+ retained, not interactive, but it is already the L1 *shape*.
202
+ * `gl/evg-a11y.js` — the a11y tree as DOM over the canvas.
203
+ * `gl/evg-textinput.js` — the `<input>` proxy.
204
+ * `inspect/` — a structural path per element and `EVGDrawCmd.node`.
205
+
206
+ Every piece of an L1 web host is on the shelf. What is missing is the one
207
+ that ties them: a painter that walks the **tree**, keeps its nodes, and is
208
+ told what changed.
209
+
210
+ ### 3.2 L1 — a retained DOM painter (`html/evg-dom.js`)
211
+
212
+ One DOM node per element, created once, patched thereafter:
213
+
214
+ | `elementType` | Host node | Notes |
215
+ | --- | --- | --- |
216
+ | container | `<div>` `position:absolute; left/top/width/height` from EVG; background, border, radius, shadow, opacity as CSS | `overflow` other than `visible` → `overflow:hidden` and a child layer `<div>` that carries `transform: translate(-scrollLeft, -scrollTop)`; a scroll is then a compositor-only style write |
217
+ | text | one `<div>` per **EVG line**, `white-space: pre`, the run's font, at the line box EVG placed | the browser must not wrap: EVG already broke the paragraph, and the PDF broke it in the same place. Bidi forced off as `evg-html.js` does (U+202D … U+202C) |
218
+ | image | `<img>` inside a clipping `<div>` | the `object-fit: cover` crop as `EVGHTMLRenderer` already does it |
219
+ | path | an inline `<svg>` with one `<path d>` per element | rings and `fill-rule="evenodd"` verbatim, exactly as `evg-html.js` — this is the one place SVG is chosen inside a DOM tree, and the reason the file's own header gives for SVG |
220
+ | a control (`role` set, or `a11yFocusable`) | the semantic element: `<button>`, `<input>`, `<div role=…>` | the a11y attributes go on the node itself; `evg-a11y.js`'s mirror is not needed for a tree that IS the DOM |
221
+ | a field (`InputCtl`) | a real `<input>` / `<textarea>` styled from the element | the proxy becomes the field; `applyEdit(value, selStart, selEnd)` is unchanged |
222
+
223
+ **Ordering.** DOM paint order is tree order plus stacking; EVG's is the
224
+ display list's, which the hit test walks backwards. Overlays and absolutes
225
+ are already placed after the flow (README, "Overlays"), so an overlay's node
226
+ appended last in its stacking context draws last. `z-index` is not in the
227
+ CSS subset and need not be: the tree order the list already emits is the
228
+ order the nodes are appended in.
229
+
230
+ **Transitions.** Two choices, and the recommendation is the boring one: keep
231
+ `EVGTransition` as the clock and write the *showing* value to the node, so
232
+ `ui:test`'s timing gates still hold and a transition on iOS and on the web
233
+ are the same code. CSS `transition` is available as an optimisation for
234
+ paint-only properties once the identity exists — it is the thing the current
235
+ painter cannot do at all.
236
+
237
+ **Fonts.** The demos lay out with `SimpleTextMeasurer` — a measured advance
238
+ table from a browser's sans fallback — and draw with the system sans. With a
239
+ DOM host the draw face is the same face; the table is a snapshot of it. An
240
+ `EVGTextMeasurer` backed by `CanvasRenderingContext2D.measureText` (the
241
+ metrics `evg-html.js` already caches per face) closes the loop: the wrap the
242
+ engine computes is the wrap the browser draws (D3).
243
+
244
+ **What it is checked against.** The same `parity.mjs` that holds the SVG
245
+ painter to the WebGL one, run on this painter; `ui:demo:page` for the text
246
+ session; the a11y gates with the mirror switched off and the tree asked
247
+ directly. The number to beat is the SVG painter's 0.022 %.
248
+
249
+ ### 3.3 L2 — native CSS layout, as a mode
250
+
251
+ The mapping surface is small. Across `gallery/realtrainer/web/realtrainer.css`
252
+ and the eighteen `gallery/ui/demo/*.css` sheets:
253
+
254
+ ```
255
+ 2410 lengths in px, 44 in %, 6 in vh, 4 in rem, 1 in fill — no hp or em in these sheets
256
+ 457 display: flex 3 display: grid 3 display: none 1 display: block
257
+ 339 flex-wrap: nowrap (explicit) 15 flex-wrap: wrap
258
+ 16 position: absolute 13 position: relative
259
+ 81 lines that name something CSS does not have: overlay-*, evg-*, scrollbar-*, hp, fill, @vars
260
+ ```
261
+
262
+ | EVG | CSS | Delta |
263
+ | --- | --- | --- |
264
+ | `display: flex` and the flex properties | the same | `align-items` defaults to `flex-start` here and `stretch` in CSS; `flex-wrap` initialises to `wrap` here and `nowrap` in CSS — so the emitter **writes both explicitly on every flex box** |
265
+ | `gap`, `justify-content`, `align-content` | the same | — |
266
+ | `display: grid`, tracks, `repeat`, `minmax`, areas, spans, `subgrid` | the same | no `auto-fit` / `auto-fill` on this side; the browser has it, so nothing to lose |
267
+ | `%` | the same | percent padding on the vertical axis was fixed to resolve against width, as CSS does (`PLAN_CSS_LAYOUT_AND_FONTS.md` 4.1 rule 9) |
268
+ | `hp` | `calc()` against the parent height, or `%` on a height property | not expressible on a width property without `calc()`; unused in the sheets above |
269
+ | `fill` | `flex: 1` in a flex parent | used once, as a spacer in a row |
270
+ | `vw` / `vh` | the same | on paper EVG says the page area; on the web it is the viewport, which is what the browser says too |
271
+ | `@media`, `@vars`, `var()`, themes | the same, or resolved by `EVGStyleSheet` before emission | resolving first keeps the palette sheet-level, which is the point of `@vars` |
272
+ | overlays (`overlay`, `overlay-side`, …) | **none** | EVG places them; the node gets `position:absolute` at the computed rectangle. CSS anchor positioning is not a target |
273
+ | `line-height: normal` = `1.15em` for the sans fallback | the face's own | the same face, so the same answer — but the browser's, not the table's |
274
+ | `evg-surface-effect`, `scrollbar-*`, `backdrop-filter` | dropped, native scrollbars, `backdrop-filter` | the ripple is a GPU pass with no DOM equivalent |
275
+
276
+ Where this stops being a mapping is **text**: the browser wraps a `<span>`
277
+ itself, its heights feed its own flex pass, and `calculatedHeight` is then a
278
+ number nobody drew. In this mode EVG's layout is not run at all for the
279
+ screen; the hit test is DOM events, the a11y tree is the DOM, the inspector is
280
+ the browser's, and the PDF is laid out separately by EVG from the same tree
281
+ and sheet. The check is the one `PLAN_CSS_LAYOUT_AND_FONTS.md` §6.3 already
282
+ names as option B: *"emit real CSS flex and diff against EVG frames in
283
+ tests"* — the box-model and grid oracles, extended with the demo pages.
284
+
285
+ This is a legitimate product shape — a Ranger-controlled DOM application —
286
+ and it is the shape that gives up the engine's claim. It should be built
287
+ after L1, if at all, and named as a different mode rather than as a faster
288
+ painter.
289
+
290
+ ### 3.4 Threads on the web
291
+
292
+ Today: everything on the main thread, `requestAnimationFrame` calls
293
+ `app.tick`, `app.frame()`, and the painter, in that order.
294
+
295
+ Proposed: the compiled engine in a **Worker**. The main thread owns the DOM
296
+ (or the WebGL context via `OffscreenCanvas`), forwards pointer, wheel and
297
+ keyboard events to the worker, and receives one message per frame:
298
+
299
+ * for the WebGL painter, the `EVGSceneBinary` — three `Int32Array`s and a
300
+ string pool, **transferred**, so the cost is the hop and not a copy;
301
+ * for the DOM painter, the host-tree diff (D2) in the same fixed-point
302
+ record shape.
303
+
304
+ Text measurement in the worker: `SimpleTextMeasurer` is a table and needs
305
+ nothing; `OffscreenCanvas.getContext("2d").measureText` is available in
306
+ workers, so the canvas-backed measurer of §3.2 runs there too. Fonts must be
307
+ loaded in the worker's `FontFaceSet` as well as the page's, or the worker
308
+ measures the fallback — the same trap `evg-html.js`'s `clearFontMetrics`
309
+ exists for.
310
+
311
+ Latency: hit testing is 0.03 ms, so a press costs one `postMessage` round
312
+ trip, which is well under a frame. What this buys is that a 12 ms layout on
313
+ a chart-heavy slide no longer blocks input or the compositor, and a scroll
314
+ on a kept list is a style write on the main thread with nothing computed
315
+ there.
316
+
317
+ ---
318
+
319
+ ## 4. Apple
320
+
321
+ ### 4.1 What exists
322
+
323
+ `apple/EvgPainter.swift` over a `CGContext`; a `UIView` host for iPhone and
324
+ iPad, a SwiftUI `Canvas` host on the watch. `frame()` is cached and repainted;
325
+ every command is rasterised every frame. The page draws its own text field
326
+ and caret; the view becomes first responder so the keyboard rises. The
327
+ dashboard lays out with EVG's estimate and paints with the platform's sans.
328
+ `apple/README.md` already names the next step for scroll: a `CGLayer` per
329
+ scroll layer, painted once per `buildSeq`, blitted per frame.
330
+
331
+ ### 4.2 L1 on UIKit and on SwiftUI
332
+
333
+ **UIKit** is the direct translation of §3.2: a `UIView` (or, for cost, a
334
+ `CALayer`) per element or per kept subtree, `frame` from EVG, a `UITextField`
335
+ where the tree has a field — the web proxy pattern, with `UITextInput` doing
336
+ what `beforeinput`/`input` do — and `UIAccessibilityElement`s from
337
+ `EVGA11yTree`, which `PLAN_ACCESSIBILITY.md` §13 already sketches for macOS.
338
+ Scroll layers are `CALayer.position` writes, off the engine entirely.
339
+ `UIScrollView` stays out for the reason `DashboardView.swift` gives: the page
340
+ has its own scroll container and a second owner makes two.
341
+
342
+ **SwiftUI** is the more interesting host and the more constrained one. The
343
+ fit is the `Layout` protocol (iOS 16, macOS 13, watchOS 9): a container whose
344
+ `placeSubviews` reads `calculatedX/Y` off EVG and whose `sizeThatFits`
345
+ returns the root's size. Each element becomes a small `EvgNodeView` switching
346
+ on `elementType`: `Text` (one per EVG line, `.fixedSize()` so SwiftUI does
347
+ not wrap it again), `TextField`, `Image`, a `Path` for vectors, a `ZStack`
348
+ for a container. What SwiftUI then gives:
349
+
350
+ * identity through `.id(key)` and `ForEach` over keyed children, so a
351
+ `withAnimation` on a rectangle change is a real animation and
352
+ `EVGTransition`'s work can be handed over per property;
353
+ * native `TextField` with the system keyboard, selection, dictation and
354
+ autofill — the P3 bridge that `PLAN_INPUTS.md` could not attach anywhere;
355
+ * `.accessibilityLabel`, `.accessibilityAddTraits`, `.accessibilityValue`
356
+ from the `a11y*` fields — VoiceOver on the tree, not a mirror;
357
+ * `Material` for `backdrop-filter`, `.shadow`, `.clipShape` for radii.
358
+
359
+ And the two costs that decide which apps qualify:
360
+
361
+ * **View count.** SwiftUI diffs the whole view tree on a state change and is
362
+ known to fall over in the low thousands of live views. RealTrainer's phone
363
+ frame is 177 commands; the dashboard's tablet frame is 352; a pptx slide
364
+ can be 10 000. The line is roughly *chrome-shaped apps yes, document apps
365
+ no* (§7), and the fallback inside one app is a `Canvas` subview for a
366
+ subtree the host marks as a picture — a chart, a slide.
367
+ * **Text.** `Text` measures itself with CoreText; EVG must measure with the
368
+ same thing or the runs will not fit their boxes. A CoreText-backed
369
+ `EVGTextMeasurer` (`CTLineGetTypographicBounds` on a `CTLine` from the same
370
+ `CTFont` the surface picks) is the piece to build first, and it improves
371
+ the **existing** CoreGraphics painter on the same day — today `ui/ios` lays
372
+ out with the estimate and draws with the platform face, which the README
373
+ calls "the honest choice" only because nothing better was wired.
374
+
375
+ ### 4.3 L2 — SwiftUI's own layout
376
+
377
+ For completeness, what maps and what does not:
378
+
379
+ | EVG | SwiftUI | |
380
+ | --- | --- | --- |
381
+ | `flex-direction: row` / `column` | `HStack` / `VStack` | ✓ |
382
+ | `gap` | `spacing:` | ✓ |
383
+ | `justify-content: space-between` / `-around` / `-evenly` | `Spacer()`s between children | ✓, by construction |
384
+ | `justify-content: center` / `flex-end` | leading/trailing `Spacer()` | ✓ |
385
+ | `align-items` | `alignment:` on the stack | ✓, no `stretch` on the cross axis without `.frame(maxWidth: .infinity)` |
386
+ | `flex-grow` | `.frame(maxWidth: .infinity)` + `layoutPriority` | approximate — SwiftUI divides remaining space equally among flexible children, not by weight |
387
+ | `flex-shrink`, `flex-basis` | none | ✗ |
388
+ | `flex-wrap: wrap` | none in `HStack`; a custom `Layout` | ✗ natively |
389
+ | `min/max-width/height` | `.frame(minWidth:…)` | ✓ |
390
+ | `display: grid` | `Grid` (iOS 16) with `gridCellColumns`; `LazyVGrid` with `GridItem(.flexible)` | fixed columns and spans ✓; `fr` weights, `minmax`, named areas, `subgrid` ✗ |
391
+ | `position: absolute` | `.offset` / `.position` in a `ZStack` | ✓ |
392
+ | overlays | `.overlay` / `.popover` | ✓ for the anchor, placement rules differ |
393
+
394
+ A SwiftUI `Layout` that fills the gaps is EVG's flex algorithm, in Swift,
395
+ which the compiler already emits. So the recommendation for Apple is the
396
+ `Layout`-protocol host of §4.2 and not a stack emitter: the same numbers, one
397
+ implementation, and print parity kept.
398
+
399
+ ### 4.4 Threads on Apple
400
+
401
+ The engine is generated Swift with no UIKit in it. Layout, the display list
402
+ and the host diff run on a background queue or an actor; CoreText measurement
403
+ is thread-safe; what crosses to the main actor is the `EVGDisplayList` (as
404
+ today) or the host-tree diff. `CADisplayLink` drives painting only. The
405
+ scroll layer as a `CGLayer` from `apple/README.md` is unchanged by any of
406
+ this and is the cheaper first step on the CoreGraphics painter.
407
+
408
+ ---
409
+
410
+ ## 5. Android
411
+
412
+ ### 5.1 What exists
413
+
414
+ `android/EvgPainter.kt` over `android.graphics.Canvas`, a Java2D twin for
415
+ checks without a device, a `View` host in `ui/android` and
416
+ `realtrainer/android`, an `InputConnection` for the soft keyboard, and the
417
+ ripple as an AGSL `RuntimeShader` post-pass. Fonts: the platform's sans in
418
+ four styles; layout with the estimate, as on iOS.
419
+
420
+ ### 5.2 L1 on Compose
421
+
422
+ Of the three platforms this is the cleanest fit, because Compose's layout
423
+ protocol is single-pass measure-and-place, which is what EVG produces:
424
+
425
+ ```kotlin
426
+ Layout(content = { children of the EVG node as composables }) { measurables, constraints ->
427
+ // EVG has already laid the tree out; the placeable is told its size and put at its rectangle
428
+ layout(w, h) { placeables.forEachIndexed { i, p -> p.place(x[i], y[i]) } }
429
+ }
430
+ ```
431
+
432
+ * **Identity:** `key(el.key)` inside the children lambda, so a reordered
433
+ keyed list moves composables rather than recreating them; unkeyed children
434
+ get the inspect path.
435
+ * **Text:** `BasicText` for each EVG line with `softWrap = false`, and an
436
+ `EVGTextMeasurer` backed by Compose's `TextMeasurer` (`rememberTextMeasurer`
437
+ / `TextMeasurer(...)`, foundation 1.5+), which is usable outside
438
+ composition — one instance owned by the engine thread, since nothing
439
+ documents it as shareable across threads — the D3 measurer for this
440
+ platform.
441
+ * **Fields:** `BasicTextField` where the tree has a field; the P3 bridge
442
+ that `PLAN_INPUTS.md` left blocked gets a host that has a field, and the
443
+ `InputConnection` written for RealTrainer becomes the field's own.
444
+ * **Semantics:** `Modifier.semantics { role, contentDescription, stateDescription, … }`
445
+ from the `a11y*` fields — TalkBack on the tree itself.
446
+ * **Scroll layers:** `Modifier.graphicsLayer { translationY = -scrollTop }`
447
+ on the layer's content composable, a `RenderNode` property write with no
448
+ recomposition; or, where the app is happy to hand scrolling over,
449
+ `verticalScroll` with the offset fed back to `EVGElement.scrollTop`.
450
+ * **Ripple:** `Modifier.clickable` with the platform's indication replaces
451
+ the AGSL pass; `backdrop-filter` stays a `RenderEffect`.
452
+ * **Paths:** `Canvas {}` with a `Path` from the rings, even-odd fill —
453
+ Compose's `Canvas` is a composable, so a chart subtree is one node.
454
+
455
+ No view-count ceiling of SwiftUI's kind: Compose skips unchanged composables
456
+ by parameter equality, and `LazyColumn` exists for the document case, though
457
+ EVG's own culling already answers that (177 commands for a 40× diary).
458
+
459
+ ### 5.3 L2 — Compose's own layout
460
+
461
+ | EVG | Compose | |
462
+ | --- | --- | --- |
463
+ | `flex-direction` | `Row` / `Column` | ✓ |
464
+ | `gap` | `Arrangement.spacedBy` | ✓ |
465
+ | `justify-content` | `Arrangement.SpaceBetween` / `SpaceAround` / `SpaceEvenly` / `Center` / `End` | ✓ |
466
+ | `align-items` | `verticalAlignment` / `horizontalAlignment`; `Modifier.fillMaxHeight` for `stretch` | ✓ |
467
+ | `flex-grow` | `Modifier.weight(n)` | ✓, weighted — closer than SwiftUI |
468
+ | `flex-shrink`, `flex-basis` | none | ✗ |
469
+ | `flex-wrap: wrap` | `FlowRow` / `FlowColumn` (foundation-layout 1.4+) | ✓ for `wrap`, `align-content` partial |
470
+ | `min/max-*` | `Modifier.sizeIn` | ✓ |
471
+ | `display: grid` | none — `LazyVerticalGrid` is a virtualised list, not CSS grid | ✗ (`fr`, `minmax`, areas, `subgrid`) |
472
+ | `position: absolute` | `Box` with `Modifier.offset` / `Modifier.absoluteOffset` | ✓ |
473
+ | overlays | `Popup` / `DropdownMenu` anchoring | ✓ for the anchor, placement rules differ |
474
+
475
+ Same conclusion as SwiftUI, with a wider native subset: the gaps (`shrink`,
476
+ `basis`, grid) are exactly the places a `Layout` with EVG's numbers would go,
477
+ so the L1 host is the recommendation and a `Row`/`Column` emitter is not.
478
+ The old `View` system with Google's `FlexboxLayout` is closer to CSS than
479
+ Compose is, and is not where Android is going; not recommended.
480
+
481
+ ### 5.4 Threads on Android
482
+
483
+ The engine is generated Kotlin with no `android.*` in it (the stubs in
484
+ `androidstubs/` exist so the host type-checks without an SDK). Layout, list
485
+ and diff run on `Dispatchers.Default`; the `TextMeasurer` measures there;
486
+ the result is posted to the main thread as a `State` the `Layout` reads.
487
+ Choreographer drives painting only. On the existing `View` host the same
488
+ split applies with the `EVGDisplayList` as the message, which is a smaller
489
+ change than Compose and worth doing first.
490
+
491
+ ---
492
+
493
+ ## 6. The four decisions, shared by all three
494
+
495
+ **D1 — who owns geometry.** EVG (L1), by default. The platform (L2) only as a
496
+ mode with an oracle, and only on the web today.
497
+
498
+ **D2 — identity across frames, and a diff.** The seam is a list of commands
499
+ that "carries no id" on purpose. An L1 host needs, per element, a handle that
500
+ survives a rebuild and a statement of what changed. Proposal: a fifth derived
501
+ list beside the display list, the hit test, the a11y tree and the inspector —
502
+ `EVGHostTree` — that walks the laid-out tree on the same pass and emits
503
+ **ops** rather than a picture:
504
+
505
+ ```
506
+ CREATE path parentPath index elementType (paint, geometry, text lines, a11y)
507
+ UPDATE path bits: GEOMETRY | PAINT | TEXT | A11Y | SCROLL
508
+ MOVE path parentPath index
509
+ REMOVE path
510
+ ```
511
+
512
+ * The key is `EVGInspect`'s path — `0/3/k:share` — so a keyed child keeps its
513
+ node across a reorder, and an unkeyed one is what it is today: structural.
514
+ `EVGReconcile` decides what "the same element" means; this reads the answer.
515
+ The path is an address, not an identity: an element of another type, tag,
516
+ id or clipping at a described address (a scene change that puts a scroller
517
+ where a card was) is a `REMOVE` and a `CREATE`, its subtree made with it,
518
+ so a host never has to turn one kind of node into another in place.
519
+ * The `UPDATE` bits come from what the engine already tracks: the style
520
+ cache's `layoutClean()` / `nothingChanged()`, `paintStamp` on the element,
521
+ the layer's `layerShiftX/Y`, and a text-lines stamp from `EVGTextEngine`.
522
+ * It is pure Ranger, like the display list, and so it compiles to ES6 for
523
+ the DOM host, Swift for SwiftUI and Kotlin for Compose. Written once.
524
+ * It has the same binary form as the scene (`int_buffer` records, a string
525
+ pool), so it crosses a thread the way the scene does.
526
+
527
+ Nothing below the seam changes: the painters keep reading the display list.
528
+ A host chooses one or the other, and a host can mix — a SwiftUI page with a
529
+ `Canvas` subtree for the chart reads both.
530
+
531
+ **D3 — text: the platform measures, EVG breaks.** An `EVGTextMeasurer` per
532
+ platform, backed by what the host draws with: `measureText` on a canvas,
533
+ CoreText, Compose's `TextMeasurer`. EVG keeps breaking the lines and emitting
534
+ one run per line, so a screen and a PDF that share a face still break in the
535
+ same place — the invariant `PLAN_CSS_LAYOUT_AND_FONTS.md` §4 is built on. A
536
+ host that wants the platform to wrap (Dynamic Type, hyphenation) is asking
537
+ for L2 on that subtree, and says so.
538
+
539
+ **D4 — the engine off the UI thread.** Worker + transferables on the web, a
540
+ background queue on Apple, `Dispatchers.Default` on Android. The message is
541
+ the display list (binary) or the host-tree diff (binary). Input goes the
542
+ other way; the hit test is 0.03 ms, so the hop is the latency. Two things
543
+ have to move to the engine thread with the engine: the text measurer (all
544
+ three are usable there) and the clock (`app.tick(dt)`), which the hosts
545
+ already hand in rather than read.
546
+
547
+ ---
548
+
549
+ ## 7. Which apps
550
+
551
+ The examples in the question — `npm run ui:demo:web`, `npm run rt:web`, and
552
+ their `ui:ios` / `ui:android` / `rt:ios` / `rt:android` ports — are all
553
+ chrome-shaped: a few hundred elements, a stylesheet with `@media`, controls
554
+ with state, one or two scroll containers. That is the L1 shape.
555
+
556
+ | App | Frame | Shape | L1 host | L2 |
557
+ | --- | --- | --- | --- | --- |
558
+ | `gallery/realtrainer` (`rt:*`) | 177 cmds, phone | responsive page, fields, dialogs, scroll | **yes** — the reference case on all three platforms; it already has the input bridge on each | web mode, maybe |
559
+ | `gallery/ui/demo` (`ui:demo:*`) | 352 cmds, tablet | dashboard, menus, tables, a chart | **yes**; the chart subtree stays a picture | web mode, maybe |
560
+ | `gallery/pptx`, `odp` | up to 10 000 cmds | a document, one slide at a time, a deck laid out against its own faces | no — painter; the SVG one for print | no |
561
+ | `gallery/datagrid`, `rangerdb viewer` | virtualised, immediate-mode | no element tree (`PLAN_ACCESSIBILITY.md` §4) | no — painter | no |
562
+ | `gallery/book`, `docx_viewer`, `pdf_writer` | pages | print-first | no — painter, PDF | no |
563
+
564
+ The split is the one `PLAN_ACCESSIBILITY.md` §4 already draws between
565
+ declarative and immediate-mode EVG: a host tree needs a tree.
566
+
567
+ ---
568
+
569
+ ## 7.5 Watches
570
+
571
+ Apple Watch and Wear OS are not small phones, and the levels above land on
572
+ them differently. `gallery/watch_evg/WATCH_PERFORMANCE.md` has the numbers
573
+ this section leans on: on the slowest Wear parts a full declarative rebuild
574
+ of a watch face is 9–14 ms of a 16.7 ms frame, a retained frame is 1.2 ms,
575
+ and on watchOS (AOT) the same face rebuilds in about 2.6 ms.
576
+
577
+ **What the platforms allow.**
578
+
579
+ | | Apple Watch | Wear OS |
580
+ | --- | --- | --- |
581
+ | UI toolkit | **SwiftUI only** — no `UIView`, no `CALayer` of your own. The painter host is SwiftUI `Canvas` (`ui/ios/watch`), and the L1 host can only be the SwiftUI `Layout` protocol (watchOS 9+); the UIKit route of §4.2 does not exist here | Views or Compose for Wear OS; Compose is where the platform is going, and the L1 host of §5.2 is the same `Layout` composable, with `ScalingLazyColumn` and `TimeText` around it |
582
+ | Input | the digital crown (a rotation, not a distance — `scrollByCrown` in the facade), a tap, a drag; no hover, no keyboard | the rotary bezel as `MotionEvent.AXIS_SCROLL` through `onGenericMotionEvent`, a tap, a swipe; the soft keyboard exists but is rarely the answer |
583
+ | Screen | 40–49mm, round-cornered rectangle, 198–224 pt across | 454² typical, usually round: content is inset to the circle, and a page laid out for a rectangle loses its corners |
584
+ | Always-on | the app's scene is dimmed and updated about once a minute; a `TimelineView` drives it | ambient mode through `AmbientLifecycleObserver`: one update a minute, no animation, a greyscale-friendly picture |
585
+ | Cold start | AOT: the first frame is single-digit ms | a JIT: the first frame is tens of ms on the bench and hundreds on a watch core; `dex2oat` helps, class loading does not go away |
586
+
587
+ **What this changes in the stages.**
588
+
589
+ * **S0 holds as it is.** `CoreTextMeasurer` is installed in the watch model
590
+ above the app, and `AndroidTextMeasurer` is not phone-specific. The watch
591
+ bench measures with `AwtTextMeasurer`, so its `layout` column now includes
592
+ a real face.
593
+ * **S1 matters more, not less.** A 9 ms rebuild on the main thread is a
594
+ crown that stutters; on the queue it is a frame that arrives a moment
595
+ later while the crown keeps its feel. `WatchPageModel` is on
596
+ `EvgEngineQueue` now; a Wear host would be `RealTrainerView.kt` with the
597
+ rotary event posted where the drag is. Two watch rules for the queue:
598
+ in ambient mode nothing drives the clock, so the engine thread must be
599
+ idle between the once-a-minute ticks (the coalescing already makes an
600
+ idle engine cost nothing); and a watch app is killed often, so the
601
+ queue's first build should be the cheap screen (`WATCH_PERFORMANCE.md`,
602
+ condition 3).
603
+ * **S4 (Compose) is the Wear path, and S5 (SwiftUI `Layout`) is the only
604
+ Apple Watch path** — there is no UIKit fallback there, which makes the
605
+ watch the place SwiftUI's view-count ceiling is least of a problem: a
606
+ watch screen is 20–80 elements, not 5 000.
607
+ * **Accessibility is the one place the watch is easier.** VoiceOver and
608
+ TalkBack both run on the wrist, and an L1 host's nodes are the tree, as
609
+ on the phone. At L0 the mirror of §3 has no equivalent on watchOS, so
610
+ today's `Canvas` host is a picture to VoiceOver — the same wall as a
611
+ `<canvas>`, without the DOM to mirror into. That is an argument for L1 on
612
+ the watch before the phone, not after.
613
+ * **The screen is round.** EVG has no notion of a non-rectangular viewport;
614
+ a Wear host insets the page to the inscribed square or lays it out with
615
+ `padding` from the sheet's `@media`, and a curved list (`ScalingLazyColumn`)
616
+ is a platform effect an L1 host gets and a painter has to fake.
617
+
618
+ **Not built here.** No Wear OS host exists in the repository; the pieces a
619
+ port would use — the measurer, the engine thread, the painter, the surface —
620
+ are the phone's, and the bench is the evidence they fit. Nothing on the
621
+ watch side is compiled by CI, as `apple/README.md` says of the whole
622
+ directory.
623
+
624
+ ## 8. Stages, each with its gate
625
+
626
+ Ordered so that every stage improves something that ships today, and the
627
+ expensive decision (D2) is made after the cheap ones have paid.
628
+
629
+ | | What | Where | Gate |
630
+ | --- | --- | --- | --- |
631
+ | **S0** ✅ | Platform text measurers behind one Ranger class, `EVGHostTextMeasurer`, that takes ONE host function and keeps the face cache, the weight convention and the cache key; `EVGDefaultMeasurer` makes it every layout's default. Hosts: canvas `measureText` (`gl/evg-measure.js`), CoreText (`apple/Sources/CoreTextMeasurer.swift`), Skia `Paint` and Java2D (`android/src/…/AndroidTextMeasurer.kt`, `AwtTextMeasurer.kt`) | `EVGContextMeasurer.rgr` was the pattern | `evg:hostmeasurer:test` (42 checks); the responsive smoke and the RealTrainer checks in Chromium; the native hosts type-check only where a Mac or `kotlinc` is |
632
+ | **S1** ✅ | Engine off the UI thread with the **existing** painters. Web: `gl/evg-engine.js` (Worker; frames as transferred `EVGSceneBinary`, now 36 fields with corners, layer and shadow; `gl/evg-binary.js` reads them; input batched into the frame request; hooks for the hit test beside the tree) and `realtrainer/web/main-worker.js` on it. Apple: `apple/Sources/EvgEngineQueue.swift`. Android/JVM: `android/src/main/…/EvgEngineThread.kt`. Both RealTrainer native views rewritten on the queues: `draw` paints the last delivered frame and reads the app for nothing | the engine untouched but for the wider record | `evg:binary:check` (27); `rt:frame` runs both browser hosts through one check and prints pointer-down-to-frame — 1.2 ms on the main thread, 11.2 ms through the Worker, in a headless Chromium without a GPU; the native views type-check only where a Mac or `kotlinc` is |
633
+ | **S2** ✅ | `EVGHostTree.rgr` — CREATE/UPDATE/MOVE/REMOVE by the inspector's path, with GEOMETRY/PAINT/TEXT/A11Y/SCROLL bits; geometry parent-relative at scroll 0; the engine's lines; JSON. No binary form yet — the DOM host reads JSON on the same thread | beside `EVGDisplayList.rgr` | `evg:hosttree:test` (61): every CREATE rectangle is one the list drew; an unchanged tree emits nothing; a colour is PAINT alone; a keyed reorder moves and remakes nothing; a scroll is one bit on the container |
634
+ | **S3** ◐ | `html/evg-dom.js` — one node per element, patched from the ops; text as a `<div>` per line with `white-space: pre`; borders as inset shadows so children are not shifted; scroll layers as one transform; role and label on the node; transforms with the element's origin. On the responsive page and on RealTrainer as `?painter=dom`, under the canvas that takes the pointer | web | `evg:dom:check` (14): every node is where the engine put it, within half a pixel, before and after a resize, and the resize updated the nodes rather than remaking them. `rt:dom` (14): the same of RealTrainer's loader, sign-in, dashboard and document; a scene change is creates and removes; a press through the canvas opens the dashboard; a scroll is one transform on the layer and remakes nothing. **Still open:** a pixel parity against `evg-html.js`, fields as real `<input>`s, and the mirror switched off in favour of the nodes |
635
+ | **S4** | The Compose host for `realtrainer/android`: `Layout` + `EVGHostTree`, `BasicTextField`, semantics | Android | `rt:android:verify` with a recording host that counts composables the way `RecordingSurface` counts draws |
636
+ | **S5** | The SwiftUI host for `realtrainer/ios` through the `Layout` protocol | Apple | `rt:ios:verify`; a view-count ceiling asserted per scene |
637
+ | **S6** (optional) | L2 web mode: the cascade resolved by EVG, layout by the browser | web | the Chromium box-model and grid oracles, extended with the demo pages; the mode is named in the URL, never the default |
638
+
639
+ S0 and S1 are independent of each other and of everything after; both are
640
+ worth doing whether or not the rest happens. S2 is where the design in D2 is
641
+ committed and is the stage to review before writing it.
642
+
643
+ ---
644
+
645
+ ## 9. The walls, named
646
+
647
+ * **Text metrics are the whole game.** Every place a native host and EVG
648
+ disagree about a pixel is a place they measured a run differently. S0 is
649
+ first for that reason, and any host that wraps text itself is L2.
650
+ * **SwiftUI's view count.** A few thousand live views is where it slows; a
651
+ document is out, and even a chrome app needs the `Canvas`-subtree escape
652
+ hatch for a chart with 5 000 segments.
653
+ * **Unkeyed lists.** A structural path is only stable for keyed children.
654
+ The demos already key what they reorder; a list that does not will churn
655
+ nodes on every rebuild, which is what the painter does today, so it is
656
+ not a regression — but it is not the win either.
657
+ * **Mixing SVG into DOM.** A `<path>` inside a `<div>` tree is fine; a clip
658
+ stack that crosses between them is not. Paths are leaves, and a path
659
+ never clips, so the mix stays at the leaves.
660
+ * **Two scroll owners.** A native `ScrollView` / `UIScrollView` /
661
+ `verticalScroll` and EVG's own container cannot both own an offset. L1
662
+ keeps EVG's and writes it to a compositor transform; handing scrolling to
663
+ the platform is an app-level choice, per container.
664
+ * **The ripple and the backdrop blur** are GPU passes over finished pixels.
665
+ Compose has `RenderEffect`, the web has `backdrop-filter`, SwiftUI has
666
+ `Material`; the ripple itself becomes the platform's own indication, and
667
+ is not the same picture. `parity.mjs` will say so, and it should be
668
+ allowed to.
669
+ * **Print parity narrows to "same lines, native glyphs".** That is the
670
+ contract L1 keeps and the one L2 does not, and it is worth writing into
671
+ `README.md`'s first paragraph the day an L1 host lands.