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.
- package/CHANGELOG.md +1160 -19
- package/LICENSE +3 -1
- package/README.md +124 -29
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79924 -56922
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- 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.
|