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.
- package/CHANGELOG.md +1064 -20
- package/LICENSE +3 -1
- package/README.md +123 -28
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79923 -56921
- 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
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,1050 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- **Erazer moved to its own repository,
|
|
13
|
+
[terotests/Erazer](https://github.com/terotests/Erazer).** `gallery/erazer`,
|
|
14
|
+
its `erazer*` npm scripts, the CI layout-lab step, and the `/evg/erazer/`
|
|
15
|
+
page and its checks in the Pages deploy are gone from this repository.
|
|
16
|
+
The compiled `gallery/erazer/bin/*.js` that the removal left tracked are
|
|
17
|
+
deleted too.
|
|
18
|
+
- **The EVG live-build harness moved to its own repository,
|
|
19
|
+
[terotests/EvgHarness](https://github.com/terotests/EvgHarness).**
|
|
20
|
+
`gallery/evg/livebuild` and its `livebuild*` npm scripts are gone. The
|
|
21
|
+
harness clones Ranger and Erazer (or uses `RANGER_DIR` / `ERAZER_DIR`) and
|
|
22
|
+
links itself back in at `gallery/evg/livebuild`; both link paths are
|
|
23
|
+
ignored here.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **`npm run test:publish` depended on the machine it ran on.** The
|
|
28
|
+
interpreter's local time is UTC, and `runtime-conformance` compared it with
|
|
29
|
+
Node formatting in the host's zone, so the `Intl` date probes failed
|
|
30
|
+
anywhere but UTC. `tests/vitest.config.ts` sets `TZ=UTC` for every fork.
|
|
31
|
+
`docs-usage` counted every `.rgr` file on disk, so test output, self-host
|
|
32
|
+
copies of `lib/` and files a moved-out project left behind could mark a
|
|
33
|
+
legacy library as used; it now reads `git ls-files`.
|
|
34
|
+
- **`npm run build:dist:module` failed with 140 TypeScript errors.** The
|
|
35
|
+
TypeScript writer typed `buffer` as `Uint8Array`, but the es6 buffer
|
|
36
|
+
templates make an `ArrayBuffer` with a `DataView` in `_view`. `buffer` is
|
|
37
|
+
now `(ArrayBuffer & { _view: DataView })` on TypeScript, and the
|
|
38
|
+
templates attach `_view` with `Object.assign`, which gives that type.
|
|
39
|
+
The generated JavaScript behaves as before.
|
|
40
|
+
- **`npm run test:publish` passes again.** `tests/http-server.test.ts` and
|
|
41
|
+
its fixture are removed: they ran the es6 HTTP server that the plugin
|
|
42
|
+
removal took out (`start` / `stop` and `RangerJavaScriptHttpServerWriter`).
|
|
43
|
+
The C++ ownership test accepts `h->item.value()` for an optional field,
|
|
44
|
+
and the Dart self-host test checks for `List<dynamic>` in place of
|
|
45
|
+
`jsonEncode`, which the compiler no longer calls.
|
|
46
|
+
- **A document's `theme` now turns on its `.theme-<name> .class` rules.**
|
|
47
|
+
`EVGLayout` applied a document's stylesheet with an empty theme, so every
|
|
48
|
+
theme-scoped rule was dead in the live page, `measure` and every other
|
|
49
|
+
layout path: `{"theme":"dark"}` on the root parsed, round-tripped, and
|
|
50
|
+
styled nothing. The UI kit's `.theme-dark` rules for every piece now
|
|
51
|
+
apply. `evg:json:test` covers it.
|
|
52
|
+
- **UI kit `bars`: the bars sit in their row.** `.ui-bar-col` had
|
|
53
|
+
`justify-content: flex-end`, and a content-sized column with that laid its
|
|
54
|
+
bar and label out one column-height above itself, over the card's title.
|
|
55
|
+
`measure` did not report it, because nothing clips there.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **Dashboard pieces in the UI kit.** `ui_kit.mjs add` builds `tabbar`,
|
|
60
|
+
`pills`, `tiles`, `bars` and `banner` as whole pieces with their rules in
|
|
61
|
+
`gallery/ui/theme/base.css`, beside `row`, `card`, `appbar`, `chips` and
|
|
62
|
+
`field`.
|
|
63
|
+
|
|
64
|
+
- **Native generic classes on Rust.** A generic class whose body only stores,
|
|
65
|
+
moves and returns its parameter values is written as `struct History<Op>`
|
|
66
|
+
with `impl<Op: Clone> History<Op>`, and used as `History<i64>` /
|
|
67
|
+
`History::<i64>::new()`. A parameter typed by the type parameter is taken by
|
|
68
|
+
value (`op: Op`) and the call sites pass owned values. A class that extends,
|
|
69
|
+
is extended or `does` a trait, a method that needs its own handle, and a
|
|
70
|
+
template named like a Rust prelude type (`Box`, `Vec`, …) keep the copies.
|
|
71
|
+
|
|
72
|
+
- **Native generic classes.** A `class History @params(Op)` whose body only
|
|
73
|
+
stores, moves and returns its parameter values is written once, as a
|
|
74
|
+
generic class of the target: `template <class Op> class History` on C++,
|
|
75
|
+
`class History<Op>` on Java, C#, Kotlin, Dart and TypeScript,
|
|
76
|
+
`class History[Op]` on Scala, `type History[Op any] struct` on Go,
|
|
77
|
+
`class History(Generic[Op])` on Python, and one `History` class on
|
|
78
|
+
JavaScript and PHP. Uses are spelled `History<int>`, `*History[int64]` and
|
|
79
|
+
so on. The type checker still checks one class per argument list. A body
|
|
80
|
+
that needs to know its parameter, and every generic class on Rust, Swift
|
|
81
|
+
and LLVM, keeps the copies (`History_int`). `-generics-report` prints the
|
|
82
|
+
choice, `-no-native-generics` turns it off.
|
|
83
|
+
|
|
84
|
+
- **`char_length`: how many characters, on all fourteen targets.** `strlen`
|
|
85
|
+
counts the target's own unit — UTF-16 code units on JavaScript, Java,
|
|
86
|
+
Kotlin, C#, Scala and Swift, UTF-8 bytes on Rust, Go, C++ and PHP, code
|
|
87
|
+
points on Python. That is the right number for a scan and the wrong one for
|
|
88
|
+
anything a person sees: a column, a width, a padding count. `char_length`
|
|
89
|
+
is the count that does not move: `len(s)` on Python, `chars().count()` on
|
|
90
|
+
Rust, `utf8.RuneCountInString` on Go, `codePointCount` on the JVM,
|
|
91
|
+
`runes.length` on Dart, `mb_strlen` on PHP, `unicodeScalars.count` on
|
|
92
|
+
Swift, and a non-continuation-byte or non-low-surrogate count where the
|
|
93
|
+
standard library has nothing. It is the length of `(to_chars s)` without
|
|
94
|
+
building the array, and for ASCII it is `strlen`.
|
|
95
|
+
|
|
96
|
+
- **EVG live build exports one Ranger UI JSON document.** Save keeps a
|
|
97
|
+
design on this machine. **Export** copies a single `.ranger.json`
|
|
98
|
+
(`format: ranger-ui`, version 2): a semantic `ui` tree, a CSS string,
|
|
99
|
+
the machine, and a `components` manifest. Known controls collapse to
|
|
100
|
+
`rave.Switch` / `SettingsRow` rather than a track and a thumb.
|
|
101
|
+
Compact is the clipboard; Full adds `compiled.evg` and layout debug.
|
|
102
|
+
`npm run livebuild:export`.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
|
|
106
|
+
- **The Rust header is built from what the file contains.** Every output used
|
|
107
|
+
to open with the same block — `#![allow(unused_parens)]`,
|
|
108
|
+
`unused_mut`, `unused_variables`, `unused_assignments`, `dead_code` and
|
|
109
|
+
three clippy lines — whether or not the file held the shape. An allow
|
|
110
|
+
nobody needs hides the backend's own regressions: a stray `mut` or a
|
|
111
|
+
redundant paren the generator starts emitting is swallowed by the pragma
|
|
112
|
+
instead of being reported. Each line is asked for now: `unused_mut` by the
|
|
113
|
+
one `let mut` the writer could not prove, the rest by predicates over the
|
|
114
|
+
program (a parameter the body never reads, a dead store, a name whose
|
|
115
|
+
snake_case spelling is already another name here, an `if` inside an `if`,
|
|
116
|
+
seven parameters, a borrowed `Vec`). Across the twelve `friendly` studies
|
|
117
|
+
the header matches what each file needs exactly, and no study asks for a
|
|
118
|
+
line it does not need. Only `dead_code` is unconditional — a
|
|
119
|
+
program's public surface is dead code in a single-file rendering of it,
|
|
120
|
+
which says nothing about the generator. The predicates skip what the
|
|
121
|
+
class-writing loop skips: `Vector.set` has an unused parameter and is never
|
|
122
|
+
emitted, and it alone was asking every file for `unused_variables`.
|
|
123
|
+
|
|
124
|
+
- **`unused_parens` is not asked for either, because the parentheses are
|
|
125
|
+
gone.** A `let` value, an assignment's right side and a template's argument
|
|
126
|
+
slot all delimit the expression, so the pair an operator template puts
|
|
127
|
+
around its whole result comes off there: `let n: i64 = (xs.len() as i64);`
|
|
128
|
+
and `rg_substring(&s, i, (i + 1))` were the two shapes, 996 rustc warnings
|
|
129
|
+
between them. An operand with an operator on either side keeps its pair —
|
|
130
|
+
that is what makes the precedence right. The template-slot rule is
|
|
131
|
+
target-neutral, so `g.check(0 - 1)` is what every target emits now.
|
|
132
|
+
|
|
133
|
+
- **`mut` is asked of the body rather than of the type.** An object local
|
|
134
|
+
used to be `mut` whatever was done with it, and so did a collection, a
|
|
135
|
+
buffer and every parameter binding. An object local now takes `mut` only
|
|
136
|
+
when the body assigns it, writes through it, calls a method emitted
|
|
137
|
+
`&mut self` on it, or hands it where the callee takes `&mut`; a parameter
|
|
138
|
+
takes it only when the body reassigns the parameter itself. Collections and
|
|
139
|
+
`&mut` parameters keep the blanket `mut` and ask for the allow — dropping
|
|
140
|
+
it on those was measured at 43 and 11 rustc errors, every one an E0596
|
|
141
|
+
where a call site writes `&mut name` and the callee is invisible from
|
|
142
|
+
there.
|
|
143
|
+
|
|
144
|
+
- **Three Rust shapes from the same reading.** A folded object literal binds
|
|
145
|
+
`let line: CartLine = …` rather than `let mut`, unless the body writes
|
|
146
|
+
through the name after the fold — and the local is renamed where it
|
|
147
|
+
collided (`g` → `g_1`), so the scan matches the source name and the
|
|
148
|
+
compiled one. A `for` body that only calls `&self` methods on the element
|
|
149
|
+
binds `&T`: `for line in &self.lines`, not `.iter().cloned()`; the answer
|
|
150
|
+
is `rust_mut_self`, which is settled for every class before the first line
|
|
151
|
+
is written. And a constructor names `Self { … }` and gives an empty owned
|
|
152
|
+
String `String::new()`.
|
|
153
|
+
|
|
154
|
+
Measured on the Rust rendering of this compiler: **2 148 warnings → 321**,
|
|
155
|
+
with 0 rustc errors, and the rendering still compiles the compiler to
|
|
156
|
+
output byte-identical to `dist/rgrc.js`. What is left was never covered by
|
|
157
|
+
any of these allows. A shopping-cart program of the kind the playground
|
|
158
|
+
compiles now carries `#![allow(dead_code)]` alone and draws no rustc
|
|
159
|
+
warning at all; `tests/codegen-rust.test.ts` keeps it that way.
|
|
160
|
+
|
|
161
|
+
- **The front-page hero copy is the shorter two columns.** Silver Bullet:
|
|
162
|
+
Ranger is a little heavier to get started with, and a golden test when
|
|
163
|
+
you have more than one language to target. Stay DRY: AI makes generating
|
|
164
|
+
code easier; consistent validation across platforms is the remaining
|
|
165
|
+
problem.
|
|
166
|
+
|
|
167
|
+
- **Front-page bench re-run: Go is 236 ms, third after C++ and Rust.**
|
|
168
|
+
Re-running after the string-index fix (C++ 136, Rust 170, Go 236, Java
|
|
169
|
+
351, JavaScript 508, Python 971). PHP, C# and Kotlin were not on this
|
|
170
|
+
machine this run. The Platforms and Gallery sections come off the front
|
|
171
|
+
page: they are AGPL gallery work, and the Platforms copy was too specific
|
|
172
|
+
for the language page.
|
|
173
|
+
|
|
174
|
+
- **`-strict-strings` asks whether the unit is observable, and the compiler
|
|
175
|
+
now reports zero.** The first version listed every index whose subject was
|
|
176
|
+
not an ASCII literal — 322 sites, which named the language rather than the
|
|
177
|
+
defect. A scan is not a defect: `while (i < (strlen s)) { charAt s i }`
|
|
178
|
+
reads the same characters on a byte target and a UTF-16 one; only the
|
|
179
|
+
numbers differ, and the program never sees them.
|
|
180
|
+
|
|
181
|
+
The flag now reports the three ways a number escapes — a `strlen` that
|
|
182
|
+
indexes nothing, a `to_chars` offset handed to `charAt`, a `charcode` on
|
|
183
|
+
something that is not an ASCII literal — and proves the rest quiet: an
|
|
184
|
+
emptiness test, a length that indexes some string, a length that bounds an
|
|
185
|
+
index, two counts of the same string compared with each other. A length
|
|
186
|
+
against a constant or against another string's length is a **note**, not a
|
|
187
|
+
finding. `strlen` is covered, which it was not before, and that is where
|
|
188
|
+
the real defects were.
|
|
189
|
+
|
|
190
|
+
Four of them, all a count of characters someone sees: the CLI progress bar
|
|
191
|
+
padded to a different column depending on which build of the compiler wrote
|
|
192
|
+
it; `formatSource` wrapped the same file in different places, because a
|
|
193
|
+
comment holding an em dash was one column wide under Node and three in the
|
|
194
|
+
Rust and Go self-hosts; `columnNumber`, which goes into errors and into the
|
|
195
|
+
source map; and `(cc N)`, which burns a character code into generated
|
|
196
|
+
source and so must not depend on its host. Three escapers now `switch` on
|
|
197
|
+
the one-character slice instead of on `charcode` of it.
|
|
198
|
+
|
|
199
|
+
Where provenance crosses a function boundary — a scan position kept in a
|
|
200
|
+
field, a length arriving as a parameter — the source says so with
|
|
201
|
+
`@(units)` on the `def` or on the function, so the claim is visible where
|
|
202
|
+
it is made. `tests/strict-strings.test.ts` keeps the count at zero.
|
|
203
|
+
|
|
204
|
+
### Fixed
|
|
205
|
+
|
|
206
|
+
- **A string literal holding BOTH an escape and a non-ASCII character came
|
|
207
|
+
out broken.** `"merkintä... esim. \"treeni\""` compiled to
|
|
208
|
+
`merkint\uFFFD\uFFFD...`: two replacement characters where the `ä` was.
|
|
209
|
+
The parser's escape-decoding path converted the literal to a `charbuffer`
|
|
210
|
+
and copied it back one unit at a time, and since a `charbuffer` is UTF-8
|
|
211
|
+
bytes on every target now, each byte of a multi-byte character was decoded
|
|
212
|
+
by itself. It reads the string directly — `strlen`, `charAt` and
|
|
213
|
+
`substring` index the same unit as each other on any one target, and every
|
|
214
|
+
character the loop looks at (`\`, `"`, `n`, …) is ASCII. The same shape as
|
|
215
|
+
the `EncodeString` fix in the six writers, in the one place that was
|
|
216
|
+
missed; the checked-in `dist/rgrc.js` carried the damage in one of its own
|
|
217
|
+
messages and needed two bootstrap passes to converge.
|
|
218
|
+
|
|
219
|
+
- **A Rust `switch` over strings broke on a quote, a backslash or a
|
|
220
|
+
newline.** A `match` arm is a pattern, so the Rust writer wrote the case
|
|
221
|
+
literal with `(str N)` — the text and nothing else, because the
|
|
222
|
+
`.to_string()` a literal gets in expression position is not something a
|
|
223
|
+
pattern takes. Unescaped, `case "\""` came out as `"""` and rustc read
|
|
224
|
+
three tokens where one was meant; `case "\n"` put a real newline inside
|
|
225
|
+
the pattern. `(estr N)` is the same accessor with the target's own string
|
|
226
|
+
escaping applied and still no quotes of its own, and the Rust `case`
|
|
227
|
+
template uses it. Found by moving three escapers onto a string `switch`.
|
|
228
|
+
|
|
229
|
+
- **The Rust rendering of the compiler compiles the compiler.** It had type-
|
|
230
|
+
checked with zero rustc errors for years and aborted on the first file it
|
|
231
|
+
was ever given, because `RefCell` checks at run time and the self-host gate
|
|
232
|
+
only type-checked. Two causes, both the same shape — a borrow that outlives
|
|
233
|
+
the statement that took it:
|
|
234
|
+
|
|
235
|
+
A `for` head is the one place Rust keeps a temporary alive across a block,
|
|
236
|
+
so a collection reached through a field kept that object borrowed while the
|
|
237
|
+
body ran and the first `borrow_mut` panicked. And a trait method with a
|
|
238
|
+
`&mut self` receiver means the call site holds a `RefMut` for the whole
|
|
239
|
+
call — while the compiler and its writers are mutually recursive by design.
|
|
240
|
+
|
|
241
|
+
A trait family that can be re-entered through its own handle is now
|
|
242
|
+
implemented for `Rc<RefCell<C>>` rather than for `C`, so `&self` IS the
|
|
243
|
+
handle and dispatching borrows nothing. Which families those are is
|
|
244
|
+
answered from the field graph rather than declared: the trait root holds a
|
|
245
|
+
field of some class X, X holds a field of the family's type, and both call
|
|
246
|
+
through it.
|
|
247
|
+
|
|
248
|
+
`npm run selfhost:run:rust` is the new gate and holds Rust to what C++ and
|
|
249
|
+
Go already meet: build it, make it compile `compiler/Compiler.rgr`, and
|
|
250
|
+
diff the result against `dist/rgrc.js`. It is identical, all 5 596 785
|
|
251
|
+
bytes, in 7.3 s against the node host's 7.8 s.
|
|
252
|
+
`docs/plans/PLAN_RUST_REENTRANCY.md` is the write-up.
|
|
253
|
+
|
|
254
|
+
- **Rust and Go index the byte their string is made of.** `strlen`, `charAt`
|
|
255
|
+
and `substring` on both counted characters, which cost them the quadratic
|
|
256
|
+
scan — `charAt` walked from the start on every read, and on Go `[]rune(s)`
|
|
257
|
+
copied the whole string each time. `gallery/friendly/bench/strscan.rgr` at
|
|
258
|
+
120 000 characters: Rust 8 830 ms → 11 ms, which is C++ to the millisecond;
|
|
259
|
+
Go did not finish inside two minutes → 19 ms.
|
|
260
|
+
|
|
261
|
+
It was also a correctness fix. `indexOf` is `strings.Index` on Go and
|
|
262
|
+
answers a BYTE offset, so a scanner that found a delimiter and sliced at it
|
|
263
|
+
sliced in the wrong place as soon as anything non-ASCII stood before it:
|
|
264
|
+
for `"ä,b"` the head came back as `"ä,"` and the tail as `""` — the rest of
|
|
265
|
+
the text, dropped without a word. Rust had the same bug and paid an O(n)
|
|
266
|
+
`chars().count()` on every `indexOf` to hide it; that conversion is gone.
|
|
267
|
+
Rust's `charcode` read `as_bytes()[0]` all along and so disagreed with its
|
|
268
|
+
own `charAt`; Java's read `getBytes()[0]` and answered −61 where `charAt`
|
|
269
|
+
said 228. All nine runnable targets are now internally consistent.
|
|
270
|
+
|
|
271
|
+
- **A byte-hosted compiler wrote every non-ASCII literal twice encoded.**
|
|
272
|
+
The C++ self-host emitted `"a—b"` into its JavaScript output as
|
|
273
|
+
`C3 A2 C2 80 C2 94` instead of `E2 80 94`: `EncodeString` rebuilt each
|
|
274
|
+
character with `strfromcode`, which writes a code point, from what `charAt`
|
|
275
|
+
gave it, which on a byte host is a byte. True of C++ and PHP all along and
|
|
276
|
+
never noticed. A one-unit `substring` copies the unit across instead, and
|
|
277
|
+
the C++ and Go self-hosts now produce output byte-identical to the
|
|
278
|
+
node-hosted compiler's.
|
|
279
|
+
|
|
280
|
+
- **`to_chars` is the portable indexable view of text.** `def cs:[int]
|
|
281
|
+
(to_chars s)` gives Unicode code points, the same sequence on every target,
|
|
282
|
+
built once in O(n) and read in O(1). `charAt` on a `string` stays the
|
|
283
|
+
target's own unit — that is what makes it O(1) there — and is right for a
|
|
284
|
+
scanner over ASCII structure; `to_chars` is for text a human wrote, where
|
|
285
|
+
`"a😀b"` has to be three characters and not two UTF-16 units plus two.
|
|
286
|
+
Because the program names the conversion, the allocation is asked for
|
|
287
|
+
rather than hidden behind an index.
|
|
288
|
+
|
|
289
|
+
- **A `charbuffer` is a buffer of bytes, and `to_charbuffer` is a string's
|
|
290
|
+
UTF-8.** One element is one octet, 0..255, not a character; UTF-8 belongs
|
|
291
|
+
to `to_charbuffer` and `to_string`, the two operators that cross between
|
|
292
|
+
text and bytes, because a conversion cannot be made without naming an
|
|
293
|
+
encoding. The buffer itself is just bytes — one holding a JPEG is not
|
|
294
|
+
"UTF-8 bytes".
|
|
295
|
+
|
|
296
|
+
It used to be whatever the host's string happened to be made of: UTF-16
|
|
297
|
+
units on JavaScript, Kotlin and Dart, code points on Python, bytes on the
|
|
298
|
+
other eight, and on Scala a `toByte` cast that truncated anything above
|
|
299
|
+
U+00FF. `to_charbuffer` is the explicit conversion — the program asks for
|
|
300
|
+
the byte view by name — so it is the one place a single portable unit can
|
|
301
|
+
be promised, and now it is: `"a—b"` is five bytes on all of them.
|
|
302
|
+
|
|
303
|
+
Measuring it with `tests/fixtures/charbuffer_units.rgr` turned up three
|
|
304
|
+
holes as well: `to_string` on a charbuffer did not compile on Rust, Java or
|
|
305
|
+
Kotlin, `charAt` on one returned a *signed* byte on the JVM targets, and
|
|
306
|
+
Swift 6 had no `to_charbuffer` template at all. Java's conversion used the
|
|
307
|
+
platform default charset rather than UTF-8.
|
|
308
|
+
|
|
309
|
+
A `charbuffer` is `Uint8Array` on JavaScript and TypeScript, `bytes` on
|
|
310
|
+
Python and `ByteArray` on Kotlin. `RangerLispParser` holds its source in
|
|
311
|
+
one, so the JavaScript self-host now scans the same bytes the C++ one does,
|
|
312
|
+
at the same speed. `tests/charbuffer-units.test.ts` asserts the agreement;
|
|
313
|
+
`docs/plans/PLAN_STRING_INDEXING.md` is the plan this is stage 1 of.
|
|
314
|
+
|
|
315
|
+
- **What a `string` index means is now measured and written down.**
|
|
316
|
+
`strlen`, `charAt` and `substring` mean a UTF-16 code unit on six targets, a
|
|
317
|
+
Unicode code point on three and a UTF-8 byte on two, and nothing said so.
|
|
318
|
+
`tests/fixtures/string_units.rgr` and `tests/string-units.test.ts` pin each
|
|
319
|
+
target's answer, `gallery/friendly/bench/strscan.rgr` measures the other
|
|
320
|
+
half — the ordinary index scan is O(n²) on Rust and Go — and `ai/QUICKREF.md`
|
|
321
|
+
says both where `string` is documented. No behaviour changed; the defect is
|
|
322
|
+
visible now.
|
|
323
|
+
|
|
324
|
+
- **Generated-code quality is three questions now, not one ranking.** The
|
|
325
|
+
single ordering read as a verdict, and it was one reading of the generated
|
|
326
|
+
files. It is split into correctness, speed and idiom, because a target can
|
|
327
|
+
do well on one and badly on another.
|
|
328
|
+
|
|
329
|
+
*Correctness* is what `gallery/friendly/compile.sh` already measured: eight
|
|
330
|
+
targets agree on all twelve studies. The one place they do not is integer
|
|
331
|
+
width — `100000 * 100000` answers `10000000000` on JavaScript, Python, PHP,
|
|
332
|
+
Go and Rust and `1410065408` on C++, C#, Java and Kotlin, and on C++ the
|
|
333
|
+
overflow is undefined behaviour rather than a wrap.
|
|
334
|
+
`gallery/friendly/bench/intwidth.rgr` is the probe.
|
|
335
|
+
|
|
336
|
+
*Speed* is new: `gallery/friendly/bench/` is the same Ranger program, five
|
|
337
|
+
kernels, each timing itself with `wall_clock_ms`. Kernels only: C++ 260 ms,
|
|
338
|
+
Kotlin 466, C# 663, Java 760, PHP 801, JavaScript 1076, Rust 2556,
|
|
339
|
+
Python 2566, Go 143882. (Re-measured after the string-indexing work: C++
|
|
340
|
+
265, Rust 397, Kotlin 485, C# 684, Go 694, Java 753, PHP 786,
|
|
341
|
+
JavaScript 1040, Python 2607.) Two of those are the compiler's doing and are
|
|
342
|
+
written down — `charAt` is O(n) on Go and Rust, and a Ranger map is a plain
|
|
343
|
+
object on JavaScript.
|
|
344
|
+
|
|
345
|
+
*Idiom* is the score that stays on the front page, and it is measured
|
|
346
|
+
against a published twelve-check table in `gallery/friendly/README.md` so
|
|
347
|
+
each cell can be disputed against the file it came from. On this compiler,
|
|
348
|
+
after native enums on twelve targets, Python annotations, PHP typed
|
|
349
|
+
properties and the ownership work: Swift 92, Kotlin 88, TypeScript 83,
|
|
350
|
+
Dart 83, C++ 83, C# 79, Scala 79, Python 75, Rust 75, JavaScript 71,
|
|
351
|
+
PHP 71, Java 67, Go 67. This supersedes the #1024 rescoring below. Swift is
|
|
352
|
+
top and has never been compiled on the build machine, which is exactly why
|
|
353
|
+
the three axes are kept apart.
|
|
354
|
+
|
|
355
|
+
- **`wall_clock_ms` works on PHP, Dart, Scala and Swift.** They fell through
|
|
356
|
+
to the `*` template, which is `0.0`, so every duration a program measured
|
|
357
|
+
on those targets was zero.
|
|
358
|
+
|
|
359
|
+
- **The front-page hero columns are gold, not a magic runtime.** The first
|
|
360
|
+
column is still “There is no Silver Bullet.” Ranger is more like gold:
|
|
361
|
+
heavier to start with, and a golden-file test when you target more than
|
|
362
|
+
one language. The second column is “Stay Dry. Stay Foolish.” — sharing
|
|
363
|
+
code across platforms is half the problem; AI does not provide the
|
|
364
|
+
consistent validation, and Ranger does.
|
|
365
|
+
|
|
366
|
+
- **C++: a `record` nothing aliases is a value, not a `shared_ptr`.**
|
|
367
|
+
`StaticAnalyzer.analyzeClassSharing` already walks the whole program and
|
|
368
|
+
decides which classes are aliased and held; the pass already ran for C++
|
|
369
|
+
compilations; only the Rust writer read the answer. So the same study came
|
|
370
|
+
out as `struct Point` + `fn manhattan(p: &Point)` on Rust and
|
|
371
|
+
`class Point` + `int manhattan(const std::shared_ptr<Point>&)` on C++.
|
|
372
|
+
A record that verdict clears is now a value everywhere it appears — the
|
|
373
|
+
parameter is `const Point&`, `new Point(3 4)` is `Point(3, 4)` on the stack,
|
|
374
|
+
and the copying builder in study 08 returns a `Request`. The C++-specific
|
|
375
|
+
disqualifiers live beside it in
|
|
376
|
+
[`compiler/CppValueAnalysis.rgr`](compiler/CppValueAnalysis.rgr): an
|
|
377
|
+
`@(optional)` of the record (an optional object *is* the null pointer on this
|
|
378
|
+
target), a `cast` to it, a method that hands out bare `this`
|
|
379
|
+
(`shared_from_this()` needs the pointer), a field of its own type,
|
|
380
|
+
inheritance in either direction, and membership of a closed family, which
|
|
381
|
+
keeps the family's own rule. `-strict-ownership` prints
|
|
382
|
+
`ownership[cpp] class Point -> value` or the reason it did not;
|
|
383
|
+
`-cpp-shared-classes` restores the old lowering. Classes with behaviour are
|
|
384
|
+
the same question and wait for `const` member functions: a `const User&`
|
|
385
|
+
cannot call `who.label()` until the writer emits one.
|
|
386
|
+
|
|
387
|
+
Two things the value form needed that the pointer form never did. A
|
|
388
|
+
parameter the body WRITES THROUGH is `Point&`, not `const Point&`: behind a
|
|
389
|
+
`shared_ptr` the `const` was on the pointer and `p->x = 1` compiled, and on
|
|
390
|
+
a value it is on the object. `StaticAnalyzer.markVarAsMutated` has been
|
|
391
|
+
recording that fact as `needs_cpp_reference` all along; the writer suppressed
|
|
392
|
+
the `&` for class types because a `shared_ptr` never needed one. And a `T&`
|
|
393
|
+
does not bind a temporary, so `m.bump((new Point(1 2)))` goes through
|
|
394
|
+
`rg_arg_ref`, which is what that helper already existed for.
|
|
395
|
+
|
|
396
|
+
- **Every target's `for` is now that target's own loop.** The question is one
|
|
397
|
+
question -- can this loop be written over the collection instead of over an
|
|
398
|
+
index? -- so it is answered once, in
|
|
399
|
+
[`compiler/ForLoopAnalysis.rgr`](compiler/ForLoopAnalysis.rgr), and each
|
|
400
|
+
writer spells the answer its own way:
|
|
401
|
+
|
|
402
|
+
| target | was | is |
|
|
403
|
+
| --- | --- | --- |
|
|
404
|
+
| JavaScript / TypeScript | `for ( let i = 0; i < xs.length; i++)` | `for ( const v of xs)` |
|
|
405
|
+
| Python | `for i, v in enumerate(xs):` | `for v in xs:` |
|
|
406
|
+
| Go | `var i int64 = 0; for ; i < int64(len(xs)) ; i++` | `for _, v := range xs {` |
|
|
407
|
+
| Java | `for ( int i = 0; i < xs.size(); i++)` | `for ( Integer v : xs)` |
|
|
408
|
+
| Kotlin | `for ( i in xs.indices )` | `for ( v in xs )` |
|
|
409
|
+
| C# | `for ( int i = 0; i < xs.Count; i++)` | `foreach ( int v in xs)` |
|
|
410
|
+
| Dart | `for ( int i = 0; i < xs.length; i++)` | `for ( final v in xs)` |
|
|
411
|
+
| Swift | `for (_, v) in xs.enumerated()` | `for v in xs` |
|
|
412
|
+
| C++ | `for ( int i = 0; i != (int)(xs.size()); i++)` | `for ( int v : xs )` |
|
|
413
|
+
|
|
414
|
+
Two conditions, and both are safety rather than length: the body must not
|
|
415
|
+
read the index, and must not touch any NAME the collection expression rests
|
|
416
|
+
on. A foreach form takes one iterator, enumerator or borrow for the whole
|
|
417
|
+
loop, so a body that appends walks something the index form does not -- an
|
|
418
|
+
invalidated iterator on C++, a `ConcurrentModificationException` on Java and
|
|
419
|
+
Kotlin, an `InvalidOperationException` on C#, a different answer on Go and
|
|
420
|
+
Swift. Every target keeps the index loop there, and `tests/loops-native.test.ts`
|
|
421
|
+
asserts both halves on all ten. Rust already made this decision
|
|
422
|
+
(PLAN_RUST_SEMANTIC_IDIOMS K) and es5 is deliberately left out, because
|
|
423
|
+
`for...of` is ES6.
|
|
424
|
+
|
|
425
|
+
- **C++: `for ( int v : xs )`.** The generated `for` was always
|
|
426
|
+
`for ( int i = 0; i != (int)(xs.size()); i++) { int v = xs.at(i); … }`. When
|
|
427
|
+
the body neither reads the index nor touches any name the collection rests
|
|
428
|
+
on, it is a range-`for` now — a scalar element by value, everything else
|
|
429
|
+
`const T&`. Both conditions are what makes it safe rather than merely
|
|
430
|
+
shorter: a range-`for` holds one iterator pair for the whole loop, so a body
|
|
431
|
+
that pushes would walk invalidated iterators where the index form re-reads
|
|
432
|
+
`.size()` every turn. Same decision the Rust writer makes
|
|
433
|
+
(PLAN_RUST_SEMANTIC_IDIOMS K), and deliberately not an
|
|
434
|
+
`<algorithm>` / `<ranges>` lowering.
|
|
435
|
+
|
|
436
|
+
- **C++ and Rust: the preamble goes in only when the program reaches it.**
|
|
437
|
+
The ordered-map preamble was gated already; these were not. On C++,
|
|
438
|
+
`template <class T> class r_optional_union` went into every file that had a
|
|
439
|
+
union — and `Any`, the compiler's own union of every declared class, is one
|
|
440
|
+
the parser declares for every program, so the answer was always yes. `Any`
|
|
441
|
+
and its forward declarations now go in only when the program says `Any`,
|
|
442
|
+
`r_optional_union` only when some `@(optional)` names a closed family, and
|
|
443
|
+
`rg_arg_ref` only when a call site needs it. The twelve gallery studies lost
|
|
444
|
+
291 lines; study 07 is 67 lines, from 88. On Rust, `use std::rc::Rc`,
|
|
445
|
+
`use std::cell::RefCell` and the `RgAnyRef` / `rg_downcast` / `RgIdentical`
|
|
446
|
+
trio go in only when the cell can reach the output — a shared class, a
|
|
447
|
+
`@(weak)` field, a closed family, a behaviour-only trait used as a type, or
|
|
448
|
+
an inheritance family. Six of the twelve Rust studies have none of those and
|
|
449
|
+
drop all thirteen lines.
|
|
450
|
+
|
|
451
|
+
Gate for all three: `bash gallery/friendly/compile.sh` (twelve studies on
|
|
452
|
+
every target with a toolchain, every `attempts/` file still refused, six
|
|
453
|
+
targets agreeing on all twelve outputs), the C++ selfhost build, and
|
|
454
|
+
`scripts/rust-selfhost-check.sh`. The plan is
|
|
455
|
+
[`docs/plans/PLAN_CPP_IDIOMS.md`](docs/plans/PLAN_CPP_IDIOMS.md).
|
|
456
|
+
|
|
457
|
+
### Added
|
|
458
|
+
|
|
459
|
+
- **A smoke effect**, `evg-surface-effect: smoke`, and three presets for it.
|
|
460
|
+
What makes smoke read as smoke is that it curls, and curls at every size at
|
|
461
|
+
once, so the field is DOMAIN WARPED — fbm evaluated at a point two other fbms
|
|
462
|
+
have already moved. One warp gives the billows, the second the tendrils that
|
|
463
|
+
come off their edges. The bar the field has to clear rises with height, which
|
|
464
|
+
is what leaves a full floor, wisps above it and black over those rather than
|
|
465
|
+
a fog that fades out evenly; `evg-fx-height` moves that line, and at 3 or
|
|
466
|
+
more the same effect is a cloud filling the box. It is lit by comparing the
|
|
467
|
+
field with itself one step toward `evg-fx-angle` — a gradient, and what gives
|
|
468
|
+
a cloud its volume. `.fx-stage`, `.fx-cloud` and `.fx-haze` are in
|
|
469
|
+
`effect-presets.css`, so they are in the gallery demo's background picker and
|
|
470
|
+
on its contact sheet; the pixel gate holds the effect to banking up along its
|
|
471
|
+
own floor, thinning toward the top, filling the box when told to, and moving.
|
|
472
|
+
|
|
473
|
+
- **A background picker on the published effects demo**
|
|
474
|
+
([`/ui/demo/?demo=effects`](https://terotests.github.io/Ranger/ui/demo/?demo=effects)).
|
|
475
|
+
The eleven blocks of `lib/evg/gl/effect-presets.css` are in the rail, and
|
|
476
|
+
picking one TYPES it into the live stylesheet under the canvas — the box's
|
|
477
|
+
own declarations kept, the effect's replaced — after which the cascade reads
|
|
478
|
+
it like any other edit. So the picker has no privileged path into the
|
|
479
|
+
painter, and what it wrote is left in the editor to be read and changed.
|
|
480
|
+
Which element a preset lands on is decided by the plugin's LAYER, asked of
|
|
481
|
+
the painter rather than listed in the page: a source effect becomes the sky's
|
|
482
|
+
own background, a backdrop effect goes on the pane over it, because it draws
|
|
483
|
+
what is BEHIND an element and the opaque sky would paint over it a moment
|
|
484
|
+
later — so rain arrives as rain on the glass, with the stars bending through
|
|
485
|
+
it. The preset file reaches the bundle as the FILE, so a preset edited there
|
|
486
|
+
is the one the picker offers, the contact sheet paints and the pixel gate
|
|
487
|
+
checks; `page-check` reads it too and holds the round trip to it — every
|
|
488
|
+
preset offered, the file's own numbers in the display list, and the
|
|
489
|
+
stylesheet in the page saying what is on screen.
|
|
490
|
+
|
|
491
|
+
- **Three quieter surface effects, and a file of presets.** `plasma-wave`
|
|
492
|
+
(ribbons of light over a noise field), `raindrop` (drops on the pane, each a
|
|
493
|
+
sphere's lens over what is behind it — strongest at the rim, nothing in the
|
|
494
|
+
middle, so the page stays legible through the centre) and `ambient-light` (a
|
|
495
|
+
slow desaturated wash with bokeh discs in it). All three are plugins in the
|
|
496
|
+
same registry the starfield and the glass use: a name, a parameter list and
|
|
497
|
+
one GLSL function, with the box coming from the layout. Two of them are
|
|
498
|
+
BACKDROP effects, which is what makes a drop a lens rather than a sticker.
|
|
499
|
+
`lib/evg/gl/effect-presets.css` holds eleven ready blocks — five skies, two
|
|
500
|
+
plasma fields, two rains, two washes — paste-able into the editor under the
|
|
501
|
+
gallery's effects demo. `npm run evg:fx:shots` paints all of them into one
|
|
502
|
+
sheet, and `evg:fx:check` reads the same file through the engine's own
|
|
503
|
+
cascade and then against pixels, so a preset the cascade refuses fails a
|
|
504
|
+
check instead of quietly drawing the plugin's defaults.
|
|
505
|
+
|
|
506
|
+
- **The effects demo's stylesheet is editable in the page.** A textarea under
|
|
507
|
+
the canvas on `?demo=effects`, holding `effects.css`, applied as you type —
|
|
508
|
+
and what is typed goes through the WHOLE engine: `EffectsDemo.init` hands the
|
|
509
|
+
text to `EVGStyleSheet`, the cascade applies it, the layout lays the tree out
|
|
510
|
+
again, the display list carries whatever effect instances the sheet declared,
|
|
511
|
+
and the painter looks their names up. Nothing patches a parameter behind the
|
|
512
|
+
scenes, so `evg-fx-density: 6` on `.fx-sky` reaches the shader the same way
|
|
513
|
+
it does on a page nobody is editing. What the cascade refuses is reported in
|
|
514
|
+
the cascade's own words — type `#fx-sky` and it says the selector is
|
|
515
|
+
unsupported, because `EVGStyleSheet` keeps every declaration it rejected.
|
|
516
|
+
It is on this page and not on the standalone `lib/evg/gl/fx-demo.html`
|
|
517
|
+
because the bundle here carries the compiled engine and that page carries
|
|
518
|
+
only a display list built for it.
|
|
519
|
+
|
|
520
|
+
- **A switch per effect in the gallery rail**, beside the demo it belongs to.
|
|
521
|
+
The list is built from the DISPLAY LIST rather than from names written into
|
|
522
|
+
the page: it knows that the stylesheet declared four effects and what each is
|
|
523
|
+
called, and nothing more. Turning one off sets the flag the painter reads —
|
|
524
|
+
the pass is skipped, the shader never runs, a press on a sleeping card goes
|
|
525
|
+
nowhere, and nothing is rebuilt to stop drawing one shader. The ordinary CSS
|
|
526
|
+
stays: switch the glass off and the card is still a rounded, tinted,
|
|
527
|
+
`backdrop-filter`-blurred box, which is the clearest way to see where the
|
|
528
|
+
effect ends and the stylesheet begins. `page-check` drives the switch in a
|
|
529
|
+
real page and holds the painter to it — one pass fewer with the sky off, and
|
|
530
|
+
back again.
|
|
531
|
+
|
|
532
|
+
- **The surface effects are on the published gallery page** —
|
|
533
|
+
[`/ui/demo/?demo=effects`](https://terotests.github.io/Ranger/ui/demo/?demo=effects).
|
|
534
|
+
Every other demo there is a control measured against the component it copies;
|
|
535
|
+
this one is a MATERIAL, and what it shows is that the effects are declared in
|
|
536
|
+
`gallery/ui/demo/effects.css` and nowhere else. `EffectsDemo.rgr` holds no
|
|
537
|
+
shader, no clock, no parameter and no coordinate: it builds a tree, the sheet
|
|
538
|
+
says which box has which effect, the layout says where the box is, and the
|
|
539
|
+
painter looks the name up in its registry. The page's own share is four lines
|
|
540
|
+
— a press, a drag, a release and "is anything still moving" — handed to the
|
|
541
|
+
driver in `lib/evg/gl/evg-fx.js`, which now runs for any demo whose list
|
|
542
|
+
carries effects.
|
|
543
|
+
|
|
544
|
+
- **Liquid glass takes a sweep of light.** A bar crossing the pane at its own
|
|
545
|
+
angle, flaring where it meets the rim, either parked or travelling:
|
|
546
|
+
`evg-fx-sweep`, `-sweep-angle`, `-sweep-width`, `-sweep-speed`, `-sweep-at`,
|
|
547
|
+
`-sweep-edge`, `-sweep-rim` and `-sweep-duty`. The last two are what make it
|
|
548
|
+
a shine rather than a stripe: `sweep-rim` weights the bar toward the bevel,
|
|
549
|
+
where real glass catches a moving light, and `sweep-duty` gives the pass a
|
|
550
|
+
fraction of each cycle and parks it off the pane for the rest — so the pane
|
|
551
|
+
is clean glass most of the time and the glint is an event. Both are checked
|
|
552
|
+
against pixels: the flat middle comes back unchanged under a rim-weighted
|
|
553
|
+
bar, and between passes nothing on the pane is brighter than a pane with no
|
|
554
|
+
sweep at all. It is off by default, because a pane in a room with nothing
|
|
555
|
+
moving has no streak on it, and a moving one asks for frames with the same
|
|
556
|
+
`evg-effect-on: always` a starfield uses. The dashed names are the point:
|
|
557
|
+
`evg-fx-sweep-speed` reaches the shader as `p_sweep_speed` and nothing in
|
|
558
|
+
between had to learn either spelling.
|
|
559
|
+
|
|
560
|
+
- **A surface effect can be switched off, and the demo page has a switch for
|
|
561
|
+
each one.** `inst.off` on an instance: the painter skips the run, the filter
|
|
562
|
+
is not live, nothing is compiled or copied, and the driver stops handing it
|
|
563
|
+
events or counting it busy — so a press on a sleeping pool falls through to
|
|
564
|
+
what is under it. The frame is not rebuilt, which is the point: stopping one
|
|
565
|
+
shader must not mean re-uploading every buffer on the page. It is the hook a
|
|
566
|
+
page's own switch hangs on, and the one `prefers-reduced-motion` would;
|
|
567
|
+
`fx-demo.html` builds a switch per effect out of the display list and
|
|
568
|
+
remembers the choice per browser.
|
|
569
|
+
|
|
570
|
+
- **Liquid glass, as CSS.** A third plugin layer and the effect that needed it.
|
|
571
|
+
`backdrop` runs a plugin IN PAINT ORDER over what is behind the element — the
|
|
572
|
+
surface so far is copied where the element paints, the plugin writes it back
|
|
573
|
+
inside the box, and the element's own background, border and children are
|
|
574
|
+
drawn on top, sharp. That is what `backdrop-filter: blur()` has always done
|
|
575
|
+
for one hard-wired filter, now open to any plugin, and it is what a pane of
|
|
576
|
+
glass needs: one that ran as a post-pass would smear its own label.
|
|
577
|
+
`liquid-glass` is refraction rather than fog — the page behind dragged toward
|
|
578
|
+
the rim, compressed into a band a few pixels wide, split slightly into colour,
|
|
579
|
+
with a specular arc inset from the very edge so it reads as a bevel and not as
|
|
580
|
+
a border somebody drew. It knows no geometry of its own: the bend follows
|
|
581
|
+
`fxBoxDistance`, the rounded-box signed distance the preamble now hands every
|
|
582
|
+
plugin, so a pane is a lens at whatever size the layout gave it and whatever
|
|
583
|
+
`border-radius` the sheet asked for. Frosting, tint and shape stay ordinary
|
|
584
|
+
CSS beside it (`backdrop-filter`, `background-color`, `border-radius`), and
|
|
585
|
+
only the lens is the effect. Checked against stripes, where a displacement is
|
|
586
|
+
visible: the rim bends, the flat middle stands still, the page around it is
|
|
587
|
+
untouched and what is drawn over the pane stays exactly its own colour —
|
|
588
|
+
`npm run evg:fx:check`.
|
|
589
|
+
|
|
590
|
+
- **A surface effect belongs to an ELEMENT now, and is declared in CSS.**
|
|
591
|
+
`evg-surface-effect: ripple` was one effect over the whole page, with its
|
|
592
|
+
parameters as fields on `EVGElement` and its drops pushed in by the
|
|
593
|
+
application from its own `pointerdown` — every press, anywhere, rippled
|
|
594
|
+
everything, and a second effect could not exist because its parameters would
|
|
595
|
+
have had to be fields on every element in every document. Three properties
|
|
596
|
+
replace all of that: `evg-surface-effect` names what runs, `evg-effect-on`
|
|
597
|
+
(`press drag hover always`) says what starts it, and `evg-fx-<name>: <number>`
|
|
598
|
+
carries parameters the engine never reads. WHERE is the element's own border
|
|
599
|
+
box, because the layout already knows it — so an effect reflows with the box
|
|
600
|
+
it belongs to and no application holds a coordinate, a clock or a drop.
|
|
601
|
+
`EVGDisplayList` writes one instance per element under the element's `id`, and
|
|
602
|
+
the rectangle where that element paints carries `efx` so the effect has a
|
|
603
|
+
place in paint order. The original whole-surface `list.effect` is untouched
|
|
604
|
+
and still runs. `npm run evg:fx:test`.
|
|
605
|
+
|
|
606
|
+
- **Renderer plugins, and a starfield to prove it takes one.** The WebGL
|
|
607
|
+
painter now looks an effect's name up in a registry rather than branching on
|
|
608
|
+
it: `registerSurfaceEffect({ name, layer, params, frag })`, where `layer` is
|
|
609
|
+
`source` (drawn in paint order at the element's background, so the element's
|
|
610
|
+
content is painted over it) or `filter` (run over the finished surface,
|
|
611
|
+
clipped to the box, which is how the ripple bends text it knows nothing
|
|
612
|
+
about). A plugin writes one function, `vec4 fxColor(vec2 p, vec2 local)`, and
|
|
613
|
+
is handed the box, the radius, the clock and the pointer events; the rounded
|
|
614
|
+
box mask is applied in the shared `main`, so a plugin cannot paint outside
|
|
615
|
+
the element that declared it. The ripple became one of these without being
|
|
616
|
+
rewritten — `RIPPLE_CORE` is one string with two entry points. `starfield` is
|
|
617
|
+
the second: parallax star layers and two-hue nebula from hashes alone, no
|
|
618
|
+
state, no uploads, nothing in the display list but a name and nine numbers.
|
|
619
|
+
`lib/evg/gl/evg-fx.js` is the driver that turns a pointer into the events
|
|
620
|
+
they read, hit-testing the boxes the list carries. Checked in a real GL
|
|
621
|
+
context — scoped to its box, under the content, moving with the clock, and
|
|
622
|
+
leaking nothing outside — by `npm run evg:fx:check`; `npm run evg:fx:demo`
|
|
623
|
+
serves a page whose every effect comes out of a stylesheet.
|
|
624
|
+
[`lib/evg/PLAN_EFFECTS.md`](lib/evg/PLAN_EFFECTS.md).
|
|
625
|
+
|
|
626
|
+
- **The UI gallery is a github.io path.** `gallery/ui` already had the
|
|
627
|
+
tree-literal demos and the Radix-vs-Ranger playground; neither was in
|
|
628
|
+
the Pages artifact, so
|
|
629
|
+
[https://terotests.github.io/Ranger/ui/](https://terotests.github.io/Ranger/ui/)
|
|
630
|
+
404'd. `deploy-pages.yml` now builds both (`/ui/demo/` and `/ui/web/`)
|
|
631
|
+
and `/ui/` redirects to the demos while keeping `?demo=`.
|
|
632
|
+
|
|
633
|
+
- **Two worked examples, as fixtures.** `lib/evg/agent/fixtures/popover.*` is a
|
|
634
|
+
menu bar whose open menu is anchored by name and becomes a bottom sheet
|
|
635
|
+
below 600px; `connector.*` is two cards with an arrow between them and a
|
|
636
|
+
count badge on a corner, in a grid that goes to one column under a media
|
|
637
|
+
query — the arrow follows without being mentioned. Both are a `.evg.json`
|
|
638
|
+
and a `.css`, rendered at two widths with
|
|
639
|
+
`npm run agent:render -- <doc> out.png -w <w> -h <h> -css <sheet>`.
|
|
640
|
+
|
|
641
|
+
- **Popovers: a surface knows where it fits, and what to be when it does
|
|
642
|
+
not.** EVG already drew overlay surfaces in a real top layer — after the
|
|
643
|
+
whole normal tree, outside every clip — and flipped them at a page edge.
|
|
644
|
+
Four things now turn that into a menu system. `position-anchor: --file`
|
|
645
|
+
names the box to position against (an `anchor-name` or an `#id`) instead of
|
|
646
|
+
finding it among the surface's own siblings, so a menu no longer has to be
|
|
647
|
+
declared beside its trigger; it also marks the element as a surface, as does
|
|
648
|
+
the new `popover` tag. `position-area: bottom start` is the side and the
|
|
649
|
+
cross-axis alignment in one declaration, and `position-try-fallbacks:
|
|
650
|
+
"top start, right start"` is an ordered list of areas tried until one is
|
|
651
|
+
wholly on the page — `position-try-order: most-space` takes the roomiest
|
|
652
|
+
instead of the first. `fit-viewport: true` clamps a surface to the room it
|
|
653
|
+
actually has and, with `overflow` set, `scrollHeight` is the rest of the
|
|
654
|
+
menu. `presentation: anchored | sheet | fullscreen` and `sheet-below: 600px`
|
|
655
|
+
cover the case no placement can: at 390 wide an anchored menu is the wrong
|
|
656
|
+
widget, so it becomes a sheet along the bottom edge with its children laid
|
|
657
|
+
out again at the page's width. `overflow-y`/`overflow-x` are accepted and
|
|
658
|
+
set `overflow`, which this engine has one of. And `anchor()` is read in the
|
|
659
|
+
inset properties — `left: calc(anchor(right) - 12px)` — which places one
|
|
660
|
+
edge against one edge of the anchor, with both insets on an axis stretching
|
|
661
|
+
the box between them; that is the badge-on-a-corner case no area can state.
|
|
662
|
+
[`lib/evg/EVGLayout.rgr`](lib/evg/EVGLayout.rgr),
|
|
663
|
+
`npm run evg:popover:test`, and the README's
|
|
664
|
+
[Surfaces](lib/evg/README.md#surfaces-popovers-anchors-and-presentation).
|
|
665
|
+
|
|
666
|
+
- **Connectors: a line between two elements, drawn by the layout.** A
|
|
667
|
+
`connector` names two boxes (`from`/`to`, an `anchor-name` such as
|
|
668
|
+
`--orders` or an `#id`), and EVG writes its `d` on every layout pass from
|
|
669
|
+
the rectangles they came out as. `from-side`/`to-side` default to `auto`,
|
|
670
|
+
which picks the facing pair — so the same connector leaves the right edge
|
|
671
|
+
while two cards sit side by side and the bottom edge once the grid stacks
|
|
672
|
+
them on a phone. `routing` is `straight`, `orthogonal` or `bezier`;
|
|
673
|
+
`arrow-start`/`arrow-end` are `open` (stroked) or `triangle` (filled) at
|
|
674
|
+
`arrow-size`; everything else is the stroke vocabulary a `path` already
|
|
675
|
+
has. `path` is unchanged and still the right tool when the author owns the
|
|
676
|
+
geometry. [`lib/evg/EVGConnector.rgr`](lib/evg/EVGConnector.rgr),
|
|
677
|
+
`npm run evg:connector:test`.
|
|
678
|
+
|
|
679
|
+
- **Erazer turns a UI screenshot into an EVG layout.** `gallery/erazer`
|
|
680
|
+
grows colour regions, nests them, and guesses widget classes (button,
|
|
681
|
+
text field, tab, menu, checkbox, slider, label, icon) instead of tracing the
|
|
682
|
+
picture as ink the way `EvgBitmapTracer` does. Icons that remain are
|
|
683
|
+
handed to that tracer as SVG. `npm run erazer:test` paints synthetic
|
|
684
|
+
UI-library fixtures with a 5×7 face and checks the tree; `npm run
|
|
685
|
+
erazer -- in.png out.evg.json` is the CLI; `npm run erazer:web:serve`
|
|
686
|
+
is the live page (paste or pick a screenshot in the tab; also at
|
|
687
|
+
https://terotests.github.io/Ranger/evg/erazer/ once Pages deploys). `npm run erazer:shots` captures HTML/CSS widgets
|
|
688
|
+
(login, tabs, menu, toolbar, dialog, buttons, sidebar) and a
|
|
689
|
+
shadcn/ui-shaped dark zinc dashboard, plus the live page itself.
|
|
690
|
+
The live overlay is pinned to the image (not the padded stage) with a
|
|
691
|
+
**tausta** opacity slider. Same-row chips of equal height and fill with
|
|
692
|
+
one-line labels are promoted to buttons together; a wide thin bar with
|
|
693
|
+
an optional circular thumb is a **slider**.
|
|
694
|
+
- **CodeGraph diffs two git revisions.** `codegraph_cli … --diff=base..head`
|
|
695
|
+
(or `--diff=base` against the working tree) and the desktop **Diff**
|
|
696
|
+
button check each side out as a detached worktree, analyse it the way
|
|
697
|
+
Open does, and compare classes by name and members by name: fields whose
|
|
698
|
+
type changed, methods whose signature changed, and methods whose lines
|
|
699
|
+
the file's line diff touched are `changed`; the rest `added` / `removed`.
|
|
700
|
+
The explorer opens on a diff page of only the touched classes (amber /
|
|
701
|
+
green / red), class pages keep the colours on their rows, and the source
|
|
702
|
+
pane shows touched files merged, removed lines in place on red bands.
|
|
703
|
+
The desktop rail lists the opened repository's log as base / head
|
|
704
|
+
pickers, and a pull request field (`12`, `#12`, a URL) fetches
|
|
705
|
+
`refs/pull/N/head` from origin and diffs from the merge base with the
|
|
706
|
+
target branch (`gh` when installed, else the remote's default branch);
|
|
707
|
+
the CLI takes `--pr=`. Rows keep their ink and carry a mark (`+`, `−`,
|
|
708
|
+
`Δ`) with a tooltip saying why; a `+ N more` row is marked when the
|
|
709
|
+
folded members include a change, and clicking it lists every member of
|
|
710
|
+
the class in a drawer that slides in from the left and leaves the chart
|
|
711
|
+
and the source pane usable — `gallery/ui` gained `DrawerCtl`, a
|
|
712
|
+
non-modal panel any host can fill, for it. The source pane copies its selection with Ctrl+C
|
|
713
|
+
(the tab through the clipboard API, the desktop through SDL) and, in
|
|
714
|
+
the tab, measures with the Noto Sans face it draws with rather than a
|
|
715
|
+
bitmap font's fixed step, which had spread the tokens of a line apart.
|
|
716
|
+
`CodeGraphDiff` needs neither git nor the
|
|
717
|
+
compiler; the web page's EXAMPLE menu diffs `calls.rgr` against
|
|
718
|
+
`calls_v2.rgr` in the tab.
|
|
719
|
+
RangerFlow rows gained `tint` / `tintText` and ScriptEditor `lineMarks`
|
|
720
|
+
for this. `npm run codegraph:diff` is the unit suite.
|
|
721
|
+
|
|
722
|
+
- **EVG live build** (`gallery/evg/livebuild/`). A demo of streaming an EVG
|
|
723
|
+
display list to a browser while an agent constructs the tree: thinking
|
|
724
|
+
tokens, `EVGPatch` ops, Ranger source and frames on SSE. The recipes are
|
|
725
|
+
scripted (dashboard, settings, invoices) so the pictures always land; a
|
|
726
|
+
live model that wrote the same ops would use the same socket.
|
|
727
|
+
The page is a local orchestrator (`agents.mjs`): recipe is the default,
|
|
728
|
+
Codex / Claude Code / Cursor / Ollama are adapters when those CLIs are
|
|
729
|
+
present. `npm run livebuild:serve` (no keys). `npm run livebuild:withcursor`
|
|
730
|
+
drives the local Cursor Agent CLI (`agent login` or `CURSOR_API_KEY`).
|
|
731
|
+
The prompt plus **Follow up** edits the live phone; Start-over chips
|
|
732
|
+
(Dashboard / Empty) are the only wipe. `npm run livebuild:test`.
|
|
733
|
+
|
|
734
|
+
### Changed
|
|
735
|
+
|
|
736
|
+
- **Generated-code quality scores are recomputed on this compiler.**
|
|
737
|
+
The twelve `gallery/friendly` studies were compiled again after native
|
|
738
|
+
loops (#1024), C++ value records, behaviour-only traits as interfaces,
|
|
739
|
+
and the C++/Rust optional fixes. Rank is now Kotlin 76, Dart 73, C# 72,
|
|
740
|
+
Python 67, Swift 64, Rust 61, PHP 59, TypeScript 59, JavaScript 58,
|
|
741
|
+
Java 58, Scala 57, C++ 54, Go 48. PHP and Scala still emit index loops.
|
|
742
|
+
|
|
743
|
+
- **The front-page Targets section is generated-code quality status.**
|
|
744
|
+
The heading is "Generated code quality": an index of the thirteen
|
|
745
|
+
official source back ends, a score table, and the same two lists for
|
|
746
|
+
every language — what works well, and current limitations. The
|
|
747
|
+
percentage is the mean of twelve `gallery/friendly` study scores
|
|
748
|
+
(0–100, how close that file is to code a native developer would keep),
|
|
749
|
+
not a compile-success score. The "Write for the strictest target" and
|
|
750
|
+
"Extending it is not a rebuild" boxes come out. Swift 3, LLVM and WASM
|
|
751
|
+
stay out of the table.
|
|
752
|
+
|
|
753
|
+
- **The repository root, and the compiler folder, hold the product.** The root
|
|
754
|
+
had 71 markdown files, nine compiled JavaScript files, eleven buffer and JPEG
|
|
755
|
+
probes, a rustc log, a Node stack trace and a `pubspec.yaml` naming a Dart
|
|
756
|
+
package that lives under `gallery/`. It now has six documents and no build
|
|
757
|
+
output, and `.gitignore` covers every extension a compile run from the root
|
|
758
|
+
produces. Open plans moved to [`docs/plans/`](docs/plans/README.md), finished
|
|
759
|
+
ones and seven directories that are not the product — `adventofcode`,
|
|
760
|
+
`fiddle`, `rust_compiler`, `native`, `features`, `generated`, `versions` — to
|
|
761
|
+
[`legacy/`](legacy/README.md), each with a README saying what it was. The npm
|
|
762
|
+
scripts that drove `features/` and `generated/` went with them: they pointed
|
|
763
|
+
at `.rgr` files in folders that hold only `.clj`, so they had been failing.
|
|
764
|
+
README no longer offers `versions/<target>/compiler.js` as the rollback; the
|
|
765
|
+
git history of `dist/rgrc.js` is.
|
|
766
|
+
|
|
767
|
+
- **`compiler/` holds the compiler.** A walk of `Import` from the entry point
|
|
768
|
+
reaches 76 of the 134 `.rgr` files that were in the folder. The other 57 were
|
|
769
|
+
earlier editions kept beside the live file (`ng_RangerFlowParserOrig.rgr`,
|
|
770
|
+
`ng_FlowWork.rgr`, six `ng_parser*` variants), twenty probes from before
|
|
771
|
+
`tests/` existed, ten plugin samples that nothing loads by path — a compiler
|
|
772
|
+
plugin is an npm package reached with `require` — and fifteen `.bat` scripts
|
|
773
|
+
calling `ranger-compiler -compiler` on `.clj` files. `compiler/index.js`, a
|
|
774
|
+
1.0 MB generated compiler, and `compiler/package.json`, declaring version
|
|
775
|
+
2.1.61, went with them; what ships is `dist/rgrc.js`.
|
|
776
|
+
|
|
777
|
+
- **A compiler file is named after the class in it.** The `ng_` prefix came
|
|
778
|
+
from the compiler that preceded this one and none of the classes carry it.
|
|
779
|
+
59 files renamed, every `Import`, npm script, test and document rewritten in
|
|
780
|
+
the same commit so no file is reachable by two spellings (ISSUES.md #64).
|
|
781
|
+
`ng_parser_v2.rgr` became `RangerLispParser.rgr`, `ng_parser_std_match2.rgr`
|
|
782
|
+
`FlowStdMatch.rgr`, `ng_writer.rgr` `CodeWriter.rgr`.
|
|
783
|
+
|
|
784
|
+
- **The three files over 9000 lines are split by what each part does.**
|
|
785
|
+
`RangerFlowParser` 9364 → 4702 (`FlowShape`, `FlowCollect`, `FlowTypes`,
|
|
786
|
+
`FlowTree`, `FlowImport`, `FlowProcess`), `RangerRustClassWriter` 12047 →
|
|
787
|
+
3056 (`RustCall`, `RustOperators`, `RustClass`, `RustOwnership`,
|
|
788
|
+
`RustUnion`), `LowIRBuilder` 12302 → 3695 (`LowIRExpr`, `LowIRCollections`,
|
|
789
|
+
`LowIRStmt`, `LowIRObject`, `LowIRLambda`, `LowIRExtern`, `LowIROwnership`).
|
|
790
|
+
Each uses the `extension` form `FlowEnterVarDef.rgr` and `FlowStdMatch.rgr`
|
|
791
|
+
already used, so the class stays in one place and only methods move — every
|
|
792
|
+
body verbatim. Every compiler file is now under five thousand lines except
|
|
793
|
+
`Lang.rgr`, which is the `language { }` document, not a class file.
|
|
794
|
+
The emitted compiler is the same code in a different order, and compiling
|
|
795
|
+
again from it is a fixpoint.
|
|
796
|
+
|
|
797
|
+
- **The front-page hero columns are "There is no Silver Bullet." and
|
|
798
|
+
"Stay Dry."** Ranger is more like golden — a golden-file harness that
|
|
799
|
+
emits ordinary Swift, Kotlin and JavaScript, not a magic runtime. The
|
|
800
|
+
definition sits as a small footnote under the first column.
|
|
801
|
+
|
|
802
|
+
- **The gold Native section is "Native apps without duplicating the
|
|
803
|
+
logic."** One source of shared logic compiled to Swift and Kotlin,
|
|
804
|
+
called from SwiftUI and Compose with no bridge; a rule changes once
|
|
805
|
+
in the `.rgr` file. The Shopify quote and the six-up feature grid
|
|
806
|
+
come out.
|
|
807
|
+
|
|
808
|
+
- **The public site title is "Rewrite? Use Ranger."** The front page
|
|
809
|
+
heading is two lines — "Rewrite?" then gold "Use Ranger." — and the
|
|
810
|
+
document title, Open Graph title, documentation `<title>` suffix
|
|
811
|
+
(`Page | Rewrite? Use Ranger.`), and playground tab title all use that
|
|
812
|
+
line. The docs header still says Ranger.
|
|
813
|
+
|
|
814
|
+
- **Getting started step 03 is the edit loop, not the old exit-status bug.**
|
|
815
|
+
The front page's path is now: start from the starter, compile the targets,
|
|
816
|
+
change `src/Main.rgr` and run it again, then take a gallery package when
|
|
817
|
+
you need one. Generated output stays ordinary source you can open and
|
|
818
|
+
diff; the `.rgr` file is the source of truth.
|
|
819
|
+
|
|
820
|
+
- **EVG is MIT and lives in `lib/evg`.** The layout engine moved out of
|
|
821
|
+
`gallery/` (AGPL) to `lib/` (MIT) together with the image codecs it needs,
|
|
822
|
+
which are now the package `lib/image` (`Buffer`, `ImageBuffer`,
|
|
823
|
+
`RasterBuffer`, the JPEG decoders, `PNGDecoder`, `PNGEncoder`, `Deflate`;
|
|
824
|
+
moved from `gallery/pdf_writer/src` and `gallery/game_engine/lpc`).
|
|
825
|
+
`lib/zip` is the package `zip`. The parts of EVG that need the gallery's
|
|
826
|
+
rasteriser and font engine — `EVGWindow`, `EVGTextFit`,
|
|
827
|
+
`EVGContextMeasurer`, `EVGRulerView`, `EVGToolbarView` — are the AGPL
|
|
828
|
+
package `gallery/evg_window`. Every gallery project that draws through EVG
|
|
829
|
+
now has a `ranger.json` and imports it as `pkg:evg/…` (and `pkg:image/…`,
|
|
830
|
+
`pkg:evg_window/…`) instead of `../evg/…`, so `lib/evg` compiles the same
|
|
831
|
+
inside this tree and when fetched alone with `rgrc install`. See
|
|
832
|
+
LICENSING.md.
|
|
833
|
+
|
|
834
|
+
- **The JavaScript compiler compiles about twice as fast.** RtHost.rgr
|
|
835
|
+
(157 files, 108 102 lines) went from 17.9 s to 8.5 s, and the compiler
|
|
836
|
+
compiling itself from 19.9 s to 6.6 s, with byte-identical output apart
|
|
837
|
+
from the constructor lines below. Four causes, none of them in the
|
|
838
|
+
algorithms:
|
|
839
|
+
- A field with no initial value was left out of the constructor and
|
|
840
|
+
appeared on the object at first assignment, so one class had as many
|
|
841
|
+
V8 hidden classes as there were assignment orders (CodeNode: 27). Every
|
|
842
|
+
property access on such objects went megamorphic and the optimiser
|
|
843
|
+
deoptimised in a loop. The ES6 writer now initialises every field, to
|
|
844
|
+
`undefined` where there is no value; `typeof`, `== null` and
|
|
845
|
+
`JSON.stringify` read the same as before.
|
|
846
|
+
- V8 gives a field the representation of the first value stored in it.
|
|
847
|
+
`CodeNode.double_value` and `int_value` began as small integers, and
|
|
848
|
+
the first fractional literal in a program changed the representation
|
|
849
|
+
on every node already made, each of which was then rewritten on its
|
|
850
|
+
next access -- 2.4 s of the RtHost compile, paid by whichever pass
|
|
851
|
+
first walked the tree. The parser now stores such values once, when one
|
|
852
|
+
node exists.
|
|
853
|
+
- `read_file` awaits `fs.readFile`, so every function that reads a file,
|
|
854
|
+
and every function that can reach one, was emitted `async`: in the
|
|
855
|
+
compiler that was the whole import, analysis and writer chain, and each
|
|
856
|
+
call paid for a promise and a heap frame -- 6.3 GB allocated and three
|
|
857
|
+
seconds of garbage collection per compile. New `read_file_sync`
|
|
858
|
+
operator, synchronous on JavaScript, used by the compiler's own file
|
|
859
|
+
reads; the compiler now has no async method.
|
|
860
|
+
- Flags and classes live on the root context, but `hasCompilerFlag` and
|
|
861
|
+
`isDefinedClass` walked up the context chain to find them, nine million
|
|
862
|
+
and seven million times per compile. The root is now cached at fork.
|
|
863
|
+
`getOpFns`, asked for every call expression and empty nearly every
|
|
864
|
+
time, no longer builds and copies a list per level of the chain, and
|
|
865
|
+
`TTypeRegistry.isScalarPrimitive` no longer rebuilds its name list
|
|
866
|
+
for every element it compares against.
|
|
867
|
+
|
|
868
|
+
### Fixed
|
|
869
|
+
|
|
870
|
+
- **A demo that moves on its own now gets a frame without being touched.** Every
|
|
871
|
+
path that started the gallery page's clock was an INPUT — a press, a key, a
|
|
872
|
+
focus — because every demo that moved did so in answer to one. A surface
|
|
873
|
+
effect does not, so `?demo=effects` opened on a still picture of a drifting
|
|
874
|
+
sky until you poked it. The clock is started on the first paint and when the
|
|
875
|
+
switcher changes demo; it stops on the first frame whose clock says nothing
|
|
876
|
+
is moving, which is all of them until something is.
|
|
877
|
+
|
|
878
|
+
- **A liquid-glass pane wiped everything painted before it, on any real page.**
|
|
879
|
+
A backdrop effect copies the surface mid-frame, and it was copying from the
|
|
880
|
+
canvas — but a WebGL2 context asked for `antialias: true`, which is the
|
|
881
|
+
default and what every page here asks for, has a MULTISAMPLED default
|
|
882
|
+
framebuffer, and copying out of one is `INVALID_OPERATION`. The copy left the
|
|
883
|
+
texture black and the pass wrote that black back over the whole page above
|
|
884
|
+
the pane. A frame with a live backdrop now goes through the offscreen target
|
|
885
|
+
and is presented at the end. The checks had passed on the broken painter
|
|
886
|
+
because they rendered with `preserveDrawingBuffer: true`, which on this
|
|
887
|
+
driver hands out a single-sampled buffer: `fx-check.mjs` now asks for what a
|
|
888
|
+
page asks for and reads pixels back through a 2-D canvas, and fails three
|
|
889
|
+
ways without the fix.
|
|
890
|
+
|
|
891
|
+
- **The UI demo page did not work on a phone.** `gallery/ui/demo` wrote each
|
|
892
|
+
demo's own width straight onto the canvas and let the rest hang off the
|
|
893
|
+
right-hand edge, where a finger could not reach it — and the 230px rail took
|
|
894
|
+
most of a 390px screen before the stage got any. The stage is now scaled to
|
|
895
|
+
the room the viewport has (`transform` on `#stage`, the wrapper carrying the
|
|
896
|
+
laid-out size, the backing store sized for the pixels actually on screen),
|
|
897
|
+
the canvas is the demo's OWN width rather than 1240 for all of them, and
|
|
898
|
+
under 860px the rail becomes a one-line `controls` disclosure above the
|
|
899
|
+
stage. `window.__stageScale` publishes the factor, because anything driving
|
|
900
|
+
the page from outside aims at display-list coordinates.
|
|
901
|
+
|
|
902
|
+
- **A canvas demo could not be reached from the keyboard at all.** The page
|
|
903
|
+
handed `state.focus` — its own field, kept for the menubar and nothing else
|
|
904
|
+
— to every demo's `a11yJson`, so the accessibility mirror was told nothing
|
|
905
|
+
was focused on nineteen of the twenty. With a roving tabindex that means NO
|
|
906
|
+
element is a tab stop: Tab walked straight past the dropdown, the tree and
|
|
907
|
+
the table. Each demo's own focus now reaches the mirror, focus arriving by
|
|
908
|
+
Tab is reported back to the demo (`onFocus`), and `evg-a11y.js` keeps one
|
|
909
|
+
entry tab stop while an app names no focus — a roving pattern always has
|
|
910
|
+
one. Tab is also no longer swallowed: `DropdownDemo.key` answers "taken" to
|
|
911
|
+
any key at all, so the page called `preventDefault()` on Tab and the focus
|
|
912
|
+
could neither enter nor leave.
|
|
913
|
+
|
|
914
|
+
- **The keyboard moved the selection and the highlight stayed behind.** Every
|
|
915
|
+
pointer handler on the demo page started the animation clock; the keydown
|
|
916
|
+
handler painted once and stopped. A row's background is a transitioned
|
|
917
|
+
property, so the one frame a key produced was the frame the transition had
|
|
918
|
+
not started in — the tree's grey sat on the row the arrow had just left, and
|
|
919
|
+
moved only when something else repainted. Arrowing the tree, the dropdown
|
|
920
|
+
and the table now looks like what it does.
|
|
921
|
+
|
|
922
|
+
- **The table demo had no keyboard.** `main.js` gave it `key: () => false` and
|
|
923
|
+
`TableDemo` had nothing to hand a key to. It now carries a roving focus over
|
|
924
|
+
the ring its own tree publishes as focusable — the select-all box (which was
|
|
925
|
+
not focusable and now is), the sortable headers, the row boxes and the pager
|
|
926
|
+
— with the arrows to walk it, Home and End, and Enter or Space to work the
|
|
927
|
+
control you are on. `table.css` grew the `:focus` rules without which none
|
|
928
|
+
of that was visible.
|
|
929
|
+
|
|
930
|
+
- **The invoice form's Email validator never changed its mind.** The error was
|
|
931
|
+
a string assigned once in `FormDemo.init`, so the red ring and "That address
|
|
932
|
+
is missing an @." stayed on the field whatever was typed into it. It is
|
|
933
|
+
decided from the value on every rebuild now, and each message says what is
|
|
934
|
+
wrong — a missing @, a space, nothing before or after it, a host with no dot
|
|
935
|
+
— rather than that something is.
|
|
936
|
+
|
|
937
|
+
- **"Find a customer" searched nothing.** It was an `InputCtl` with a
|
|
938
|
+
magnifier drawn in front of it: a field that looks like an autocomplete and
|
|
939
|
+
filters no list. It is a `ComboboxCtl` now — the same one the metadata card
|
|
940
|
+
uses, measured against @base-ui/react/combobox — so typing filters, the list
|
|
941
|
+
opens under the box, the arrows walk it and Enter takes a row into the
|
|
942
|
+
field. `ComboboxCtl.applyEdit` is the new seam a host that lets the platform
|
|
943
|
+
do the editing needs: a keystroke and a browser edit have the same effect on
|
|
944
|
+
the list, and neither host has to reimplement it. The form claims the arrows
|
|
945
|
+
back from the text bridge for that field alone (`ownsKey`), because there
|
|
946
|
+
they walk the list rather than the caret.
|
|
947
|
+
|
|
948
|
+
- **`evg:view:check` flaked in CI on a landing-only pull request.** The
|
|
949
|
+
suite draws each scene twice — once with the view baked into the list,
|
|
950
|
+
once with it on the camera — by rewriting one `file://` HTML file and
|
|
951
|
+
navigating to it. Chromium will sometimes answer the previous document
|
|
952
|
+
for that URL, `waitForFunction` sees the leftover `__DONE__`, and the
|
|
953
|
+
comparison reads the last framebuffer: one of thirty-one checks fails,
|
|
954
|
+
the rest pass. A unique query on each load is a different navigation;
|
|
955
|
+
`gl.finish()` lands before the read; a mismatch draws both sides again
|
|
956
|
+
before it is a failure. The gallery-editors runner also printed only
|
|
957
|
+
the last thirty lines of a failed suite, which hid the `FAIL` behind
|
|
958
|
+
later PASSes.
|
|
959
|
+
|
|
960
|
+
- **The published compiler shipped a thinner `stdlib.rgr` than the one the
|
|
961
|
+
tests ran against.** `compiler/stdlib.rgr` and `lib/stdlib.rgr` had drifted:
|
|
962
|
+
the compiler's copy carried the LLVM `case` and `is` templates that let
|
|
963
|
+
`lowerShapeCase` bind its operand, and the Swift `_ = name` lines that stop
|
|
964
|
+
swiftc warning once per arm of an exhaustive `match`. Which copy you got
|
|
965
|
+
depended on where you compiled from. In the repository RANGER_LIB puts
|
|
966
|
+
`compiler/` first, so CI always exercised the fuller file; `build:dist:copy`
|
|
967
|
+
is `cp -r ./lib ./dist/lib` and the published package resolves every import
|
|
968
|
+
from there, so an npm user compiling a `match` to Swift got the warnings and
|
|
969
|
+
to LLVM got an unmatched operator. `lib/` now holds the merged file and the
|
|
970
|
+
duplicate under `compiler/` is gone. `compiler/JSON.rgr` was byte-identical
|
|
971
|
+
to `lib/JSON.rgr` and went the same way. Both imports keep their spelling;
|
|
972
|
+
resolution finds them one directory later.
|
|
973
|
+
|
|
974
|
+
- **`.claude/skills/ranger-lang/SKILL.md` said the compiler exits 0 on
|
|
975
|
+
`[FAIL]`.** It has exited non-zero since 3.5.1, and the copy under
|
|
976
|
+
`plugins/ranger/` already said so. The two copies had drifted in both
|
|
977
|
+
directions — the plugin copy still said `(obj.method()).field` does not
|
|
978
|
+
work — and are now identical and correct.
|
|
979
|
+
|
|
980
|
+
- **`@media` was silently inert in every CLI tool.** `EVGStyleSheet` evaluates
|
|
981
|
+
a media query against a viewport the caller states, and a query it cannot
|
|
982
|
+
evaluate does not apply — but `EVGStyleLoader`, which every `-css` flag goes
|
|
983
|
+
through, never stated one. So the responsive half of a stylesheet did
|
|
984
|
+
nothing in the PNG, PDF, HTML, JSON and display-list tools while the same
|
|
985
|
+
sheet worked in an application, with no warning either way: the page
|
|
986
|
+
rendered, and it rendered wrong. The tools know their page size before they
|
|
987
|
+
apply anything, and now pass it.
|
|
988
|
+
|
|
989
|
+
- **`position: absolute` was dropped inside a `display: grid` parent.**
|
|
990
|
+
`layoutGrid` left out-of-flow children out of the placement, which is
|
|
991
|
+
right — an absolute box takes no track — and then nothing laid them out at
|
|
992
|
+
all: the box kept zero size at (0,0), so an absolutely positioned `div`
|
|
993
|
+
vanished and a `path` drew its own coordinates in the page's top-left
|
|
994
|
+
corner with `left`/`top` ignored. The same element under a flex or block
|
|
995
|
+
parent was placed correctly, which made it look like a `path` bug. The
|
|
996
|
+
out-of-flow pass is now one function (`EVGLayout.layoutOutOfFlowChild`)
|
|
997
|
+
that both flow and grid run.
|
|
998
|
+
|
|
999
|
+
- **An array literal survives a call whose result is dereferenced.**
|
|
1000
|
+
`(box.take(([] _:string ( "a" "b" )))).count()` emitted
|
|
1001
|
+
`box.take("a""b").count()` — the elements where the array should be. Two
|
|
1002
|
+
elements did not parse on JavaScript or PHP; one element parsed and answered
|
|
1003
|
+
the STRING's length, so `take(([] _:string ( "abc" )))` counted 3. Every
|
|
1004
|
+
target, and the compiler reported success either way, which is why
|
|
1005
|
+
`lib/Shell.rgr` and `lib/apple/` build every argument vector with `push`
|
|
1006
|
+
rather than writing it inline.
|
|
1007
|
+
|
|
1008
|
+
`transformDotMethodCallExpr` walked the receiver — that is how it learns the
|
|
1009
|
+
receiver's type and decides this is a method call at all — and then put a
|
|
1010
|
+
COPY of the walked node into the rewritten `call`. Walking an array literal
|
|
1011
|
+
is destructive: the node's children become its elements and the node is
|
|
1012
|
+
marked `is_array_literal`, and the two together are what an array literal is
|
|
1013
|
+
after analysis. A copy is built for re-analysis and carries no analysis
|
|
1014
|
+
result, so it had the elements and no mark: a bare list. The receiver is
|
|
1015
|
+
copied before it is walked now, and that untouched copy is what the rewrite
|
|
1016
|
+
gets. ISSUES.md #85.
|
|
1017
|
+
|
|
1018
|
+
The one-element literal still goes missing on Rust, chained or not — that is
|
|
1019
|
+
#84, in the Rust writer.
|
|
1020
|
+
|
|
1021
|
+
- **A failed compile exits non-zero.** `rgrc` printed `[FAIL]` and
|
|
1022
|
+
`Compilation FAILED` and then returned success, so
|
|
1023
|
+
`rgrc x.rgr && node bin/x.js` was satisfied by that zero, ran the
|
|
1024
|
+
*previous* build, and printed what it printed before the change — the edit
|
|
1025
|
+
looked applied and the test looked green when neither was true. Every
|
|
1026
|
+
`&&` in every build script believed the zero, which is why a dozen scripts
|
|
1027
|
+
in this repository read the compiler's log instead of its status. The
|
|
1028
|
+
status now says what the report says: 1 when the run had errors (a parse
|
|
1029
|
+
error, a missing file, any compiler error, a failed `rgrc install`),
|
|
1030
|
+
0 otherwise.
|
|
1031
|
+
|
|
1032
|
+
The code is set rather than the process ended on the spot: node writes a
|
|
1033
|
+
piped stdout asynchronously, and `process.exit()` there drops the error
|
|
1034
|
+
report the status is about — `log=$(rgrc … 2>&1)` would get the status and
|
|
1035
|
+
lose the reason. The new `set_exit_code` operator in `Lang.rgr` spells
|
|
1036
|
+
that on each target (`process.exitCode` on JavaScript,
|
|
1037
|
+
`Environment.ExitCode` on C#, the same immediate exit as `exit` where
|
|
1038
|
+
stdout is unbuffered or flushed at exit).
|
|
1039
|
+
|
|
1040
|
+
- **CodeGraph web page: css / evg / zip / cpp did not open from the EXAMPLE
|
|
1041
|
+
menu.** The app could only compile what its VFS held, and the gallery
|
|
1042
|
+
pack (and the C++ fixture) were fetched only for `?example=`. A pick the
|
|
1043
|
+
app cannot serve is now handed back to the page (`consumePendingSample`),
|
|
1044
|
+
which fetches the sources and picks again. The page also awaits the
|
|
1045
|
+
app's methods — the ES6 bundle makes any path that may read a file
|
|
1046
|
+
`async`, and `selfTest()` came back as a Promise, which is why
|
|
1047
|
+
`codegraph:web:test` failed with `nav.startsWith is not a function`.
|
|
1048
|
+
- **CodeGraph: a class did not list who uses it through a method.** Rose
|
|
1049
|
+
boxes above a class came from field types only, so `Checkout` showed no
|
|
1050
|
+
`CodeGraphFixtureMain` although `main()` creates one. Classes whose
|
|
1051
|
+
methods mention or call the class are now backrefs too, with the method
|
|
1052
|
+
rows and a dashed `uses` edge from each row.
|
|
1053
|
+
|
|
10
1054
|
## [3.5.1] - 2026-09-17
|
|
11
1055
|
|
|
12
1056
|
- **The npm README still said 3.3.0.** The package was 3.5.0; the first line
|
|
@@ -22,7 +1066,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
1066
|
|
|
23
1067
|
- **`rgrc install` fetches what `ranger.json` names.** `Import "pkg:…"`
|
|
24
1068
|
used to resolve only against files already on disk, so a clean checkout
|
|
25
|
-
of a project that depends on `
|
|
1069
|
+
of a project that depends on `lib/evg` had nothing to compile
|
|
26
1070
|
against. The compiler now walks the graph, sparse-fetches each git
|
|
27
1071
|
dependency at its pinned revision, writes it into the cache
|
|
28
1072
|
(`RANGER_PKG_CACHE`, else `~/.cache/ranger/packages/<sha256>`), recurses
|
|
@@ -139,7 +1183,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
139
1183
|
cell's text lands in the third cell.
|
|
140
1184
|
|
|
141
1185
|
- **A design for the camera on the GPU.**
|
|
142
|
-
`
|
|
1186
|
+
`lib/evg/PLAN_VIEW_TRANSFORM.md` asks why a canvas rebuilds its whole
|
|
143
1187
|
display list for every frame of a pan, when the pan is a translate the vertex
|
|
144
1188
|
shader already applies for scroll layers (`uShift`) and the scene has not
|
|
145
1189
|
changed. The list would be built in scene space and carry the camera beside
|
|
@@ -156,7 +1200,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
156
1200
|
- **Two fingers pinch the canvas, and the gestures are one module.** Drag to
|
|
157
1201
|
pan, wheel to zoom, a press that does not travel is a click — every
|
|
158
1202
|
standalone had written its own, and none of them had a pinch.
|
|
159
|
-
`
|
|
1203
|
+
`lib/evg/gl/evg-gestures.js` is that handling once, for any EVG canvas:
|
|
160
1204
|
it reads the view the host keeps and hands back another, so the host goes on
|
|
161
1205
|
deciding when to paint. The anchor holds the point under the cursor, or
|
|
162
1206
|
under the midpoint of two fingers, where it is; a trackpad pinch (a wheel
|
|
@@ -251,7 +1295,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
251
1295
|
the page `EVGDisplayList.toBinary()` — three `Int32Array`s and a string
|
|
252
1296
|
pool — where it used to hand it JSON: on a board of 3,565 nodes that is
|
|
253
1297
|
5,630 ms a frame against 180, and the two bridges describe the same picture
|
|
254
|
-
to the hundredth (`
|
|
1298
|
+
to the hundredth (`lib/evg/gl/list-binary-check.mjs` holds them to it).
|
|
255
1299
|
`scene()` still answers in JSON for anything that wants to read a frame.
|
|
256
1300
|
|
|
257
1301
|
- **A flattened outline is kept on the element it belongs to.** `d` is a
|
|
@@ -824,7 +1868,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
824
1868
|
for node. 101 + 102 + 17 + 16 assertions and 24/24 target builds; the client
|
|
825
1869
|
build an app carries is 113 kB of Kotlin, which is the measured answer to
|
|
826
1870
|
whether it fits on a watch. Plan and the phases left in
|
|
827
|
-
[`PLAN_FIRESIM.md`](PLAN_FIRESIM.md).
|
|
1871
|
+
[`PLAN_FIRESIM.md`](gallery/firesim/PLAN_FIRESIM.md).
|
|
828
1872
|
- **A segmented date field, measured against the browser's own.** The
|
|
829
1873
|
calendar demo's date box was a formatted label; a person asked for the
|
|
830
1874
|
`__/__/____` editor, and shadcn has none to measure (its Date Picker is a
|
|
@@ -1046,7 +2090,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
1046
2090
|
`ranger/rt_ios.rgr` imports `RealTrainerDemo.rgr` unchanged and compiles to
|
|
1047
2091
|
19 000 lines of Swift holding the EVG controllers, the cascade, the layout
|
|
1048
2092
|
engine, the display list and the demo. Nothing about the app is written twice
|
|
1049
|
-
for Apple; `
|
|
2093
|
+
for Apple; `lib/evg/apple` paints it, as it paints the dashboard.
|
|
1050
2094
|
|
|
1051
2095
|
The facade is not a copy of the dashboard's, because the two pages are not
|
|
1052
2096
|
the same shape. The dashboard is a DOCUMENT -- a fixed width that scrolls, so
|
|
@@ -1077,7 +2121,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
1077
2121
|
|
|
1078
2122
|
### Fixed
|
|
1079
2123
|
|
|
1080
|
-
- **`
|
|
2124
|
+
- **`lib/evg`: a grid flag nothing read.** `EVGLayout.layoutGrid` set
|
|
1081
2125
|
`usingSubgrid = true` when a column template inherited its tracks from the
|
|
1082
2126
|
enclosing grid, and then never looked at it. The subgrid effect is carried
|
|
1083
2127
|
entirely by rewriting `colSpec` into the parent's pixel tracks, and the rows
|
|
@@ -1361,7 +2405,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
1361
2405
|
iPhone, iPad and Apple Watch.** The same `DashboardDemo.rgr` the browser
|
|
1362
2406
|
page, the gates and the Android port run, compiled to Swift 6 (46 039 lines,
|
|
1363
2407
|
one file) and painted with CoreGraphics through the new
|
|
1364
|
-
[`
|
|
2408
|
+
[`lib/evg/apple`](lib/evg/apple/README.md) — an `EvgSurface` protocol,
|
|
1365
2409
|
an `EvgPainter` transliterated from the Android one, and a single `CGContext`
|
|
1366
2410
|
backend that serves UIKit, SwiftUI and watchOS alike.
|
|
1367
2411
|
|
|
@@ -1394,7 +2438,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
1394
2438
|
source: Kotlin on a JVM (the Wear OS language, with a C1-only run as the
|
|
1395
2439
|
ART-quality bracket), C++ ahead-of-time and reference-counted (the watchOS
|
|
1396
2440
|
proxy), and JavaScript on Node. The Kotlin run paints through
|
|
1397
|
-
`
|
|
2441
|
+
`lib/evg/android`, the same painter and surface the `ui` and `pptx`
|
|
1398
2442
|
Android ports compile into their APKs, and writes the three PNGs.
|
|
1399
2443
|
|
|
1400
2444
|
The answer is **no, EVG is not too heavy**: the busiest screen is 0.91 ms for
|
|
@@ -2290,7 +3334,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2290
3334
|
|
|
2291
3335
|
- **Three defects in Vela that only a program calling it could reach** — each found by building six charts through the new chart API and comparing the drawing against official Vega-Lite and Vega, and none of them in the API. `%q` was **not a directive** in the time formatter, and an unknown directive answers its own letter, so every chart with `timeUnit: "yearquarter"` — which the official compiler turns into the format `"%Y Q%q"` — labelled its axis `2016 Qq` and said nothing about being wrong. A **grouped bar chart of stated width drew twenty-pixel bars**: which way round the two bands are sized depends on who decided the plot's size, and the sub-scale was always given a fixed twenty-pixel step, which is right only when nobody said how wide the plot is (then the band above it is sized *from* the sub-bands) and leaves a third of every group empty when somebody did — the sub-scale has to span the band it sits in, `[0, bandwidth('x')]`, exactly as the reference writes it. And **the key to a see-through area was drawn solid**, because a legend symbol's opacity was read only off an `opacity` channel and not off the mark (`{"type": "area", "opacity": 0.35}`), which is how every layered chart shades its filled half. All 48 goldens, 4848 drawn primitives, 44 Vega-Lite sources and the 188-example gallery corpus (172 exact, 3 random-by-construction) are unchanged by the three
|
|
2292
3336
|
|
|
2293
|
-
- **A long value in a dialog field was drawn straight out of the field, across the panel and over the sheet behind it** — `EVGWindow`'s input control drew `c.value` from the field's left edge at full length, with no window and no cut. Clipping alone does not fix it: `SoftPainter` honours a clip rectangle **per draw call** (`textInClip` tests the text's ORIGIN), so a string that starts inside the box is drawn whole, and the WebGL and SDL hosts scissor at their own granularities again. Text that must not escape a box has to be cut to the box on every backend. The cut now lives in one place, `gallery/
|
|
3337
|
+
- **A long value in a dialog field was drawn straight out of the field, across the panel and over the sheet behind it** — `EVGWindow`'s input control drew `c.value` from the field's left edge at full length, with no window and no cut. Clipping alone does not fix it: `SoftPainter` honours a clip rectangle **per draw call** (`textInClip` tests the text's ORIGIN), so a string that starts inside the box is drawn whole, and the WebGL and SDL hosts scissor at their own granularities again. Text that must not escape a box has to be cut to the box on every backend. The cut now lives in one place, `gallery/evg_window/EVGTextFit.rgr`: the window that fits the field **and** contains the caret (grown left from the caret while there is room, then right with what is left, so typing at the end scrolls the text and moving back brings the earlier text into view), the measured caret offset inside that window, a hit test measured the same way, and the label trimmer the grid already had. `EVGWindow`'s input, the grid's cell labels and the formula field all go through it. The display-list primitives moved to `EVGDisplayList` itself (`addRect` / `addFrame` / `addText` / `addClip` / `addClipEnd`) — `GridView` and `EVGWindow` each had their own encoder for the same commands — so a dialog and the grid behind it cannot drift into two encodings. `npm run datagrid:textfit:test` checks the display list rather than the picture: for every piece of text that starts inside a panel, does it end inside it too? It fails on the code from before the fix. Also: the database window smoke now addresses cells by name (`nav.goto E2`) instead of by pixel, after a merge with master moved the grid geometry under its hard-coded clicks
|
|
2294
3338
|
|
|
2295
3339
|
- **A declared empty constructor produced a body-less `__init__` on the Python target** — `ng_RangerPythonClassWriter` already emits `pass` for an empty body, but it set `hasContent = true` for any class that *declared* a constructor rather than for one whose body actually wrote something. A class with no fields, no parent and `Constructor () { }` therefore compiled to `def __init__(self):` with nothing under it, and Python failed to parse the whole module at the next method (`IndentationError: expected an indented block`). Every other target was unaffected, because only Python's block structure is whitespace. It is now measured the same way `pyWalkBody` already measures a method body — the writer's line and column before and after walking the constructor — and a `super().__init__()` call counts as content, so a subclass with an empty constructor no longer gets both the super call and a stray `pass`. Found by RangerSQL's dialect classes, which are pure behaviour and declare no fields at all. Compiler reproduces itself byte-identically; `compiler-python`, `compiler-conformance`, `compiler-selfhost`, `compiler-record`, `compiler-serialize` and `operator-coverage` pass, and `shapes`' 14 Rust failures reproduce on the unmodified compiler
|
|
2296
3340
|
|
|
@@ -2309,7 +3353,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2309
3353
|
|
|
2310
3354
|
- **The rules a photo book has to satisfy belong to the printer, so they are data now** — `gallery/book` could lay a book out and draw it; what it could not do was answer "is this ready to send". `BookPrintSpec` is a supplier's requirements as a value — trim size, bleed, outer **and gutter** safety margins, the extent's minimum, maximum and multiple, the dpi floor and target, colour space, PDF profile, whether they want crop marks — with presets for layflat 210, hardcover A4, softcover A5 and sheet-fed offset, and preflight measures the book against whichever one you name. Hard-coding any of it would have produced an engine that is confidently wrong for every supplier but one: 3 mm of bleed is a European convention and not a law, a multiple of four is one binding's arithmetic, and one service wants CMYK while the next one wants RGB. Three things stayed assertions rather than settings because they are not negotiable. **Page 1 is a recto** — odd pages right, even left, and a cover is always the right-hand side of the sheet it is bound onto. **Single pages, in reader's order**, 1, 2, 3, 4: printer spreads (32–1 / 2–31) are the press's business, and imposing them yourself is how a book comes back bound inside out — the engine designs in spreads and exports leaves, which is the whole point of a page-layout program. And **a blank page is a page**: it takes a leaf, it is counted, and it has to be *in* the file, so `padToExtent` adds real ones rather than leaving a gap that silently moves every page after it onto the wrong side of the leaf. The gutter check is the one that earns its keep: it is a **bigger** margin than the cut edges and it applies to whichever side of the page the spine is on, which is the left of a recto and the right of a verso — a face centred in a spread lands in the fold, and on a cased book the fold takes 15 mm. **The spine is arithmetic and `BookCover` does it**: leaves × caliper for the text block, plus two boards for a case or the wrap for a softcover, with the squares, the hinge and the turn-in giving the cover sheet its real size — built as its own one-page landscape document, so the same renderer, the same PDF writer and the same picture-resolution checks apply to it. It still says, in the output and in the README, to take the supplier's own template before a production run; this exists so a cover can be proofed before that template arrives and so their number can be checked against one. `npm run book:print` writes `interior.pdf` (single pages at trim + bleed, with a **TrimBox** — 210 mm square is delivered as 216 mm square), `cover.pdf`, a `print.json` manifest carrying the fields a print-on-demand API asks for, `preflight.txt`, and `render.sh` — the exact commands with the computed page sizes already in them, because a cover PDF made at a retyped width has the title on the front board and the spine somewhere in the picture. It found a live trap on the way: **the committed `evg_pdf_tool.js` build predates its own `-bleed` flag** and contains no reference to bleed at all, so running it with `-bleed` accepted the argument, warned about nothing, and wrote a PDF at trim size — invisible until a press trims into the artwork. The script recompiles the tool before using it. 40 more assertions, **116 on the engine now, running on JavaScript, Go and Python**, plus the 64 editor ones and 17 in a real browser
|
|
2311
3355
|
|
|
2312
|
-
- **The book engine got an editor, and it runs in the page** — `gallery/book` could lay a book out and print it; it could not be *used*. Now it can: select a frame, drag it, resize it from eight handles, insert text and picture frames, reorder pages, undo. The frame around the canvas is the **shared** one the spreadsheet, the document and the deck already use — a fourth toolbar in a fourth style would have been the wrong kind of new code — and the selection chrome goes into the same `EVGDisplayList` as the pages, after them, so every host draws a complete editor without knowing what a handle is, and an exported book has none of it. Four rules are taken from the slide editor because they were right there and are right here (an id is not an index; history is snapshots; a drag is one edit; editing is a mode) and one is this program's own and is the whole difference: **every geometry edit re-flows** — resizing a text frame does not move text inside it, it moves text onto other pages, and an editor that does not re-flow after a drag is showing a book that does not exist. The gesture with no equivalent in a slide editor is **Link flow** (Ctrl+L): select two text frames and the story runs from one into the other. It moves no text; it changes where the text is *allowed* to go. **It is not a Node app with a browser attached.** `web/standalone/` compiles the entire engine — model, flow, preflight — to JavaScript and puts it IN the page: the browser hands it font bytes and photographs and draws the display list it gets back through WebGL 2, so a pointer move is a function call rather than a round trip and there is no host process at all (`npm run book:web`). The Node-hosted variant stays as `book:window`, for driving the editor from a script. Three findings came out of building it, all of the kind a screenshot makes look fine. **Snapping per-delta means a frame on a guide can never be dragged off it** — every one-point step is pulled back onto the line — so a drag is now measured from where it *began*. **A host that hands over every font face with `addFaceBytes` gets a renderer that draws correctly and measures with a 3×5 bitmap fallback**, because only `loadFontBytes` sets `hasFont`; the page was drawing the title in exactly the right typeface, at the wrong widths, so nothing ever wrapped. And **a WebGL page needs `@font-face` for the faces the engine measured with**, since `evg-webgl.js` rasterizes runs with the browser's canvas 2D and cannot see the engine's. The serverless page's self test now asserts the title wraps *and* that it is set in the display face, which is the pair that catches all three. Four things moved into `
|
|
3356
|
+
- **The book engine got an editor, and it runs in the page** — `gallery/book` could lay a book out and print it; it could not be *used*. Now it can: select a frame, drag it, resize it from eight handles, insert text and picture frames, reorder pages, undo. The frame around the canvas is the **shared** one the spreadsheet, the document and the deck already use — a fourth toolbar in a fourth style would have been the wrong kind of new code — and the selection chrome goes into the same `EVGDisplayList` as the pages, after them, so every host draws a complete editor without knowing what a handle is, and an exported book has none of it. Four rules are taken from the slide editor because they were right there and are right here (an id is not an index; history is snapshots; a drag is one edit; editing is a mode) and one is this program's own and is the whole difference: **every geometry edit re-flows** — resizing a text frame does not move text inside it, it moves text onto other pages, and an editor that does not re-flow after a drag is showing a book that does not exist. The gesture with no equivalent in a slide editor is **Link flow** (Ctrl+L): select two text frames and the story runs from one into the other. It moves no text; it changes where the text is *allowed* to go. **It is not a Node app with a browser attached.** `web/standalone/` compiles the entire engine — model, flow, preflight — to JavaScript and puts it IN the page: the browser hands it font bytes and photographs and draws the display list it gets back through WebGL 2, so a pointer move is a function call rather than a round trip and there is no host process at all (`npm run book:web`). The Node-hosted variant stays as `book:window`, for driving the editor from a script. Three findings came out of building it, all of the kind a screenshot makes look fine. **Snapping per-delta means a frame on a guide can never be dragged off it** — every one-point step is pulled back onto the line — so a drag is now measured from where it *began*. **A host that hands over every font face with `addFaceBytes` gets a renderer that draws correctly and measures with a 3×5 bitmap fallback**, because only `loadFontBytes` sets `hasFont`; the page was drawing the title in exactly the right typeface, at the wrong widths, so nothing ever wrapped. And **a WebGL page needs `@font-face` for the faces the engine measured with**, since `evg-webgl.js` rasterizes runs with the browser's canvas 2D and cannot see the engine's. The serverless page's self test now asserts the title wraps *and* that it is set in the display face, which is the pair that catches all three. Four things moved into `lib/evg` because the book editor was the second caller: `EVGImageDecode` (PNG/JPEG bytes → pixels — it had been sitting in the deck viewer with a note on it saying it was not deck-specific), `EVGSelectChrome` (where the eight handles are and which edges each one owns — both editors now number their handles from the same file and read `applyResize`'s answers out of it), `EVGContextMeasurer` (EVG text measurement backed by a host's own renderer, which is what makes the flow engine measure with the faces the screen paints with), and `EVGDisplayList.offsetBy` / `.appendFrom`. The sample book is typeset rather than filled in — the showcase's editorial palette, Cinzel and Josefin Sans, a leaf ornament as a vector frame, captions on the pictures, first-line indents after the first paragraph — because a sample set entirely in the UI face proves nothing about a page-layout engine. **80 assertions on the engine (76) run on JavaScript, Go and Python; 64 more cover the editor and the host seam; 17 run in real headless Chrome on WebGL**, and `pptx`, `docx_viewer`, `datagrid` and the EVG toolbar suites are unchanged by the extractions (33 / 220 / 100 / 125 / 25, 244, 113, 23)
|
|
2313
3357
|
|
|
2314
3358
|
- **A tool that says what this reader does not understand, and then most of the answer** — every fixture in the pptx gallery was written by the same hand as the reader, so every fixture is understood by construction; two decks from outside turned up six defects in an afternoon, all of them the same shape: an element walked straight past, drawing nothing and saying nothing. `npm run pptx:audit -- deck.pptx` walks every part of a package through the reader's OWN parser and reports what it never looks at, most-used first, in **two** lists — *known and deliberately not drawn* (3-D, embedded fonts, hyperlinks, per-script font fallbacks, animation beyond the build) and *UNREAD, nobody decided about these*. The difference between those lists is the difference between a decision and an oversight, and it turns "the slide looks wrong" into a work order; `npm run pptx:audit:check` runs it over every fixture and fails when one says something nobody has decided about. Pointed at the two decks the list was 24 and 39 kinds long, and it was mostly saying one thing: **text is inherited, not stated**. DrawingML states nine levels of list style, `a:lvl1pPr` … `a:lvl9pPr`, and this reader read the first — and only from the shape itself — so every sub-bullet in every real deck came out in the top level's size, colour and indent. A list style is nine levels now, every field paired with a "was this stated" flag because they are MERGED down a chain: master `p:txStyles` → the master's own placeholder → the layout's placeholder → the shape → the paragraph. The order cost a defect to get right: a deck built by anything but PowerPoint leaves `p:txStyles` as a generic black nobody meant and puts the real typography on the master's PLACEHOLDER, which sits above it — and the placeholder merge was applying its default run properties to every run before the level each paragraph sits at had been consulted, which marked them all as sized and coloured. Beside it: **`a:normAutofit`**, which is PowerPoint writing down how far it already shrank the text to make it fit (ignoring it is an overflow with a known cause); **bullets with their own colour, size and face**, and a layout that uses the deck's own `marL`/`indent` rather than a level's worth of invented indent; a paragraph's own `<a:buNone/>` and a stated `marL="0"` treated as decisions rather than silences; **`a:highlight`**, the colour behind a run; and the same fallback gap the emoji had one block down the codepoint chart — **a bullet is a geometric shape** (● ○ ■ ▪) that the text face does not have, so every list drew a column of empty boxes until Noto Sans joined the fallback pool. The two decks now report 9 and 22 kinds, **all deliberate, nothing unread**. `31-inherited-text.pptx` pins it down and round-trips byte for byte, which needed the writer to state what the levels resolved to — there is no master left to inherit from — and turned up one more instance of a defect this repository keeps meeting: `to_int` FLOORS, so `emu(-18pt)` biased and floored a negative twice and wrote -228601 EMU where the file said -228600, moving every hanging bullet by a fraction of a pixel
|
|
2315
3359
|
|
|
@@ -2346,7 +3390,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2346
3390
|
|
|
2347
3391
|
- **A dot asks what the thing can do** — member completion, with a guess at the type behind it. Typing `.` opens the list with no prefix (because `body.` is already a question) and the answer comes from reading BACKWARDS for where the receiver was declared and classifying what is on the right of the `=`: an array literal offers `push` / `map` / `length`, a quoted string offers `toUpperCase` / `split`, `sheetRows(…)` is known to return rows so its members are an array's, `usedRows(…)` offers `toFixed`, an object literal offers **its own keys**, and `rows[i]` takes one step down through the subscript. There is no type checker and there is not going to be one on a keystroke — what there is, is the four ways a value gets its shape in a report script, which covers nearly every line anyone writes; when none of them matches the guess is `any` and the list is everything, which is what a type checker says about `any` too and is better than an empty popup. The member lists are **the methods ComponentEngine actually implements**, read off the engine rather than off the JavaScript standard: offering `.flatMap()` because JavaScript has it would be offering to write a line that cannot run. The Ranger plugin answers `this.` from the file — every `fn`, `sfn` and `def` in it, labelled `method`, `static` and `field` — because in a language with no imports to chase, the file is the scope. 121 unit checks and 83 in the browser
|
|
2348
3392
|
- **The editor suggests, and its clipboard stopped lying** — autocomplete is the third question a language plugin answers, beside colours and problems: `complete(lines, line, col)` returns labelled candidates, two letters open the list, **Ctrl+Space** opens it whatever the prefix, arrows choose, Enter or Tab accepts and Escape closes. Accepting **replaces the word being typed** rather than appending to it. What is in the list is the plugin's opinion and the order is the whole of it: for TSX the **workbook API first** (`sheetRows`, `formatNumber`, `param`), then keywords, then every word already in the file — a report script is mostly the first and third and almost never `instanceof`; for Ranger the same shape with operators first. Neither plugin parses to answer, because `complete` runs on a keystroke. The popup is drawn into the display list like everything else, so WebGL and OpenGL both get it without knowing what a completion is, and it is **mirrored into the DOM** for assistive technology — the textarea becomes a `combobox` with `aria-expanded` and `aria-activedescendant`, the options live in a hidden `listbox`, and the live region says "6 suggestions, sheetRows selected". **Copy and cut now answer from the model.** Letting the hidden textarea serve Ctrl+C was right for a selection inside one line — the mirror holds exactly that — and silently wrong for every other: three selected lines copied one, and cut copied without deleting. `selectionText()` and `cutSelection()` come from the buffer now, the chords are let through to the browser so `copy` / `cut` / `paste` fire on the textarea (which is the only way to reach the system clipboard without a permission), and `metaKey` counts as well as `ctrlKey`, which is what made it look broken on macOS. Also: double click selects a word and triple click the line, through `EditorWord`, so a word means the same thing to the mouse as it does to Ctrl+arrow. And the header's **fps meter is gone** — the page deliberately does not redraw when nothing changed, so a frames-per-second reading of an idle editor is 2 and means nothing; the numbers worth watching (lines, tokens, lex and check times, draw commands) are in the status bar and come from the editor rather than from the loop. 102 unit checks and 77 in the browser
|
|
2349
|
-
- **A click handed the keyboard back, and the page redrew for nothing** — two defects a real browser session found that 52 passing checks did not. **Clicking into the code editor and then typing did nothing at all**: the browser moves focus on mousedown *after* the handler runs, and the canvas is deliberately not focusable (it is `aria-hidden`; the focusable element is the textarea that mirrors the caret's line), so the default action took the keyboard straight off it and gave it to `<body>`. One `preventDefault` on `pointerdown` fixes it — and the reason no test caught it is that **every test reached the editor with the keyboard**, by Tab or a programmatic focus, and none had ever clicked; `keyboard.mjs` now opens with a real mouse click and types after it. Second, the page ran at **4 fps** because the animation loop called `scene()` on every frame — build the display list, walk it, serialize it to JSON, ~1.4 ms — and then compared the string with the last one and threw it away. The loop now asks `revision()` first, a few string joins over the document version, the caret, the selection, the scroll line and the blink phase, and builds a scene only when that changed: idle went from 60 rebuilds a second to **2**, which is the caret, and which is why the blink now comes off the clock rather than off a frame counter. Third, and this one is everyone's: a Chrome profile put **160 ms — 36% of the frame — in `getShaderParameter`**, because `renderDisplayList` compiled and linked both of its shader programs on *every call*, and asking for `COMPILE_STATUS` is synchronous — it makes the CPU wait for a compile the driver was entitled to defer. `
|
|
3393
|
+
- **A click handed the keyboard back, and the page redrew for nothing** — two defects a real browser session found that 52 passing checks did not. **Clicking into the code editor and then typing did nothing at all**: the browser moves focus on mousedown *after* the handler runs, and the canvas is deliberately not focusable (it is `aria-hidden`; the focusable element is the textarea that mirrors the caret's line), so the default action took the keyboard straight off it and gave it to `<body>`. One `preventDefault` on `pointerdown` fixes it — and the reason no test caught it is that **every test reached the editor with the keyboard**, by Tab or a programmatic focus, and none had ever clicked; `keyboard.mjs` now opens with a real mouse click and types after it. Second, the page ran at **4 fps** because the animation loop called `scene()` on every frame — build the display list, walk it, serialize it to JSON, ~1.4 ms — and then compared the string with the last one and threw it away. The loop now asks `revision()` first, a few string joins over the document version, the caret, the selection, the scroll line and the blink phase, and builds a scene only when that changed: idle went from 60 rebuilds a second to **2**, which is the caret, and which is why the blink now comes off the clock rather than off a frame counter. Third, and this one is everyone's: a Chrome profile put **160 ms — 36% of the frame — in `getShaderParameter`**, because `renderDisplayList` compiled and linked both of its shader programs on *every call*, and asking for `COMPILE_STATUS` is synchronous — it makes the CPU wait for a compile the driver was entitled to defer. `lib/evg/gl/evg-webgl.js` caches the two programs and their attribute and uniform locations per GL context now, so the DataGrid's own WebGL page gets the same fix. A full redraw of the editor measures 2.9 ms of GL plus 1.4 ms of scene building on software rasterization in headless Chrome. Regression checks added for both: a mouse click that hands over the keyboard, and an idle second that redraws a handful of times rather than every frame (58 checks)
|
|
2350
3394
|
- **The SQL box shows the schema and offers examples written for it** — a prompt with no manual is not much help, and the one thing a person needs before writing a `SELECT` is the names of the tables and their columns. The box now lists them (`sales(id*, region, country, month, revenue, units)`, a star marking a key column, which is what decides whether a sheet built from the query can be edited) and offers five one-click examples generated from that schema — first rows, a `GROUP BY` over a text column and a numeric one, a `LIKE` filter, a `ORDER BY … DESC LIMIT`, and a `COUNT(*)` — so each of them runs as it stands instead of being a template to adapt. Both are asked of the session every time the box opens, so they are right after a reconnect too. A test parses every generated example and checks it names a table of this database. Dialog labels are also trimmed to the panel now (`EVGTextFit.fitLabel`, the same cut the input already used): a status line or a column list is text somebody else wrote, and it must not run out of the window
|
|
2351
3395
|
|
|
2352
3396
|
- **The chart API runs in the browser, at `/evg/chart-api/`, and JavaScript calls it directly** — every other page in the EVG gallery is drawn ahead of time and published as a file, which proves the API built a chart *once, on a build machine, in Node*, and says nothing about whether the thing that drew it still runs where a reader is. `gallery/vela/tools/vela_chart_web.rgr` compiles the API to a browser bundle that publishes the compiled classes themselves — `VlChart`, `VlChartMark`, `VlDataset` — so the page's **primary language is JavaScript** and `chart.bar().x("region")` in the editor is those classes' own methods with no binding layer in between (the page counts the calls with a `Proxy` and hands the finished chart back to `VelaChartApi.draw`). **Ranger is the second tab**, through a small dispatcher over the same methods, and **the same chart in either language draws the same SVG byte for byte** — which is the whole claim the page makes, so the browser check asserts it for all eight presets rather than assuming it. The rest of the entry describes that dispatcher: `chart.bar().x("region")` calls `VlChart.bar()` and then `VlChartMark.x("region")`, the chart redraws as you edit, and the Vega-Lite those calls built and the Vega it compiles to are on the tabs beside it. Editing the **data** redraws too — the difference between a live page and a picture of one. It is an interpreter over the real methods rather than a second implementation: there is no chart type in that file and no specification is written by hand, so a name the API does not have is refused *by name* (`line 2: a chart has no method 'colour'`, `'x' needs its argument in quotes: region`, `the data has no column called 'profit'` — the last being the API's own check running client-side, because the dataset is there). `npm run showcase:api` opens it in Chromium and checks **through** the page: every preset draws a different chart, an unknown method is refused, editing the data redraws, the inferred column types are shown, the specification and the Vega are both present, and a page error or console error fails the run — **22 checks**. One thing worth knowing for anyone shipping a Ranger bundle to a browser: the compiler writes a `#!/usr/bin/env node` shebang, and a `<script>` is not a shell — left in, the page loads nothing and says nothing.
|
|
@@ -2359,7 +3403,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2359
3403
|
|
|
2360
3404
|
- **A connection window for the database-backed workbook (Ctrl+D)** — until now the only sign of which engine a sheet was talking to was a line in the status bar. The window shows engine, data source and table as editable fields, and under them what was actually negotiated: rows loaded, the key columns that make the sheet writable (or the reason it is read only), what the engine can do for itself (`DBCapabilities.describe()`) and what Ranger had to do instead (`DBSession.lastFallback`). Connecting is the **host's** part, like opening a file picker: **Connect** leaves a request behind (`takeDbRequest()` → `"connect"` with driver, DSN and table) and the host opens the engine and calls `bindDatabase`; `web/serve.mjs` services it with `GridDbLauncher`, and a host that ignores it keeps the sheet it already has. Typing another engine into the field and pressing Enter switches a live workbook — DuckDB to RangerDB and back — which the window smoke now drives through the same events a person sends. Demo rows are only seeded into `:memory:`: pointed at a file, a table that is not there is reported rather than invented
|
|
2361
3405
|
|
|
2362
|
-
- **Vela has a chart API: marks and channels, called rather than written** — `gallery/vela/src/VlChart.rgr`. A program that wanted a chart used to have to write a specification out as text, which is how `gallery/datagrid` builds its twenty chart types: three hundred lines of string concatenation, quote by escaped quote. The API writes the same Vega-Lite **value** instead, and `toSpec()` hands it to `VlCompile` and `VlRuntime` exactly as pasted text is handed to them — the fluent surface is a writer of specifications and not a second implementation of anything, which is AntV G2's idea and the reason none of this can drift away from the engine. Four things follow from that and from holding the data (`VlDataset`, an ECharts-style value beside the chart rather than inside it): a **view's encoding is inherited by its marks**, so an area and the line on top of it are two marks with one set of axes and one legend, resolved at emit time into a `layer` that states every channel in full; a **channel need not say what it is** — a column of ISO dates is temporal, of numbers quantitative, of anything else nominal, read off the rows (Observable Plot's ergonomics); a **column the data does not have is an error** rather than the empty axis Vega-Lite would draw and not mention; and what the API does not cover is **written out by hand into the same specification**, where the layer that knows refuses it by name (`a bin written with 'step' is not compiled here`) instead of the API keeping its own list of what the layer below supports. Checked three ways: `tests/chart_test.rgr` builds ten charts and compares the **Vega** each compiles to against a hand-written Vega-Lite specification, then runs each one (71 checks, and two matching refusals fail rather than pass); `tools/reference/chart_api.mjs` gives the six charts of `tools/vela_chart.rgr` — a file with no specification text in it — to the **official** Vega-Lite and Vega and compares the ink to a quarter of a pixel (**6 of 6**); and `tests/run_cpp.sh` requires the same 71 checks to pass and the same six charts to come out byte for byte from a `g++` binary with no JavaScript underneath. It also has a page of its own on the published EVG showcase — **Charts, called**, `
|
|
3406
|
+
- **Vela has a chart API: marks and channels, called rather than written** — `gallery/vela/src/VlChart.rgr`. A program that wanted a chart used to have to write a specification out as text, which is how `gallery/datagrid` builds its twenty chart types: three hundred lines of string concatenation, quote by escaped quote. The API writes the same Vega-Lite **value** instead, and `toSpec()` hands it to `VlCompile` and `VlRuntime` exactly as pasted text is handed to them — the fluent surface is a writer of specifications and not a second implementation of anything, which is AntV G2's idea and the reason none of this can drift away from the engine. Four things follow from that and from holding the data (`VlDataset`, an ECharts-style value beside the chart rather than inside it): a **view's encoding is inherited by its marks**, so an area and the line on top of it are two marks with one set of axes and one legend, resolved at emit time into a `layer` that states every channel in full; a **channel need not say what it is** — a column of ISO dates is temporal, of numbers quantitative, of anything else nominal, read off the rows (Observable Plot's ergonomics); a **column the data does not have is an error** rather than the empty axis Vega-Lite would draw and not mention; and what the API does not cover is **written out by hand into the same specification**, where the layer that knows refuses it by name (`a bin written with 'step' is not compiled here`) instead of the API keeping its own list of what the layer below supports. Checked three ways: `tests/chart_test.rgr` builds ten charts and compares the **Vega** each compiles to against a hand-written Vega-Lite specification, then runs each one (71 checks, and two matching refusals fail rather than pass); `tools/reference/chart_api.mjs` gives the six charts of `tools/vela_chart.rgr` — a file with no specification text in it — to the **official** Vega-Lite and Vega and compares the ink to a quarter of a pixel (**6 of 6**); and `tests/run_cpp.sh` requires the same 71 checks to pass and the same six charts to come out byte for byte from a `g++` binary with no JavaScript underneath. It also has a page of its own on the published EVG showcase — **Charts, called**, `lib/evg/showcase/pages/chart_api.tsx`, the only page there that no specification was written for: each chart is printed with the calls that built it, and those lines are read out of the generating tool's own source at the markers around each chart's calls, so the code the page shows and the code that drew the page cannot drift apart (without the source the tool refuses to write the page). Getting code onto an EVG page turned up one thing worth knowing: **JSX text is tokenised as if it were code**, so a double quote in it opens a string and `x("region")` came out as `x(region )` — a line goes on as a string *literal* in an expression container instead, which is the one place the parser keeps a quote. Design, surface and what is still missing (interaction, a host API, composition beyond a layer) in [`gallery/vela/CHART_API.md`](gallery/vela/CHART_API.md). `npm run vela:chart`, `npm run vela:showcase && npm run showcase`
|
|
2363
3407
|
- **`gallery/rangersql` — a SQL parser, generator and dialect transpiler in Ranger, and RangerDB's SQL front end.** SQLGlot-inspired rather than a port: tokenizer → parser → one common AST → generator, with a `SqlDialect` answering only the questions engines disagree on. The AST is a flat arena addressed by int (`SqlAst.nodes[45]`, not `node.parent.args["expressions"][0]`) because a tree of objects with parent pointers is pleasant on a GC target and painful on Rust, C++ and Swift; a node carries a kind, its text, flags and `(role, child)` pairs. Expressions are precedence climbing — one table of binding powers instead of term/factor/comparison/conjunction — and comments are tokens the parser hands to the node they were written beside, so `SELECT 1 /* c1 */ + 2 /* c2 */, 3 /* c3 */` round-trips exactly. Covers SELECT with joins, CTEs, set operations, subqueries, CASE, CAST, window functions, `IN`/`BETWEEN`/`LIKE`/`IS NULL`, array subscripts and JSON operators, plus INSERT/UPDATE/DELETE. Measured against **SQLGlot's own `tests/fixtures/identity.sql`** (980 statements it regenerates character for character, vendored with attribution): 519 identical, 3 differing, 458 not yet parsed — with the remaining buckets named in the README (DDL is 175 of them) and the identical count asserted as a baseline so a change that quietly parses less fails. Cross-checked against SQLGlot itself by `tools/sqlglot_oracle.py`: of the 522 statements RangerSQL parses, SQLGlot reads 520 of its outputs as the *same query*, the two exceptions being optimizer-hint comment placement. `Sql.transpile` moves a statement between SQLite, Postgres and MySQL (`IFNULL` ↔ `COALESCE`, `LIMIT 10, 20` → `LIMIT 20 OFFSET 10`, `x::INT` → `CAST(x AS INT)`, backtick quoting, no `NULLS LAST` on MySQL). **`gallery/rangerdb/src/SqlFront.rgr`** plans a parsed statement into the `QuerySpec` / `DBMutation` RangerDB already executes, so `capabilities.sqlText` is now true and the contract suite's SQL section runs on RangerDB, SQLite and DuckDB alike (65/65 each, 94 for RangerDB with its engine-internal and front-end tests); the planner refuses rather than guesses, naming what was in the way. The whole library is ordinary Ranger with no host bindings: 12/12 targets compile, and the RangerDB suite including the SQL front end passes identically on JavaScript, Python and native C++
|
|
2364
3408
|
- **`gallery/rangerdb` — a database API with three engines behind it, and a DataGrid that edits through it.** The interface is a `QuerySpec` (table, columns, filter, sorts, groupBy, aggregates, offset, limit) rather than SQL text, because a `query(sql)`-only API forces every backend to own a SQL parser before it can answer anything — RangerDB would have had to be a SQL implementation before it could be a database, and the grid would be building strings to say "sort by column 3". SQL stays as an escape hatch and `SqlText.rgr` renders a spec into it for the engines that speak it, with every value travelling as a `?` parameter rather than as text. Results cross as `DataChunk`s — column vectors, a chunk at a time — which is what DuckDB's execution format already is, what a virtualised grid page wants and what a chart series is. Backends declare `DBCapabilities` and `DBSession` executes whatever they did not claim over the chunks that came back, so an engine is never asked to pretend. **RangerDB** itself is columnar and chunked: row groups of 1024, a scan that hands out the stored vectors when nothing is deleted, column pruning driven by the spec, streaming aggregation that never materialises the rows it sums, early scan stop for an unsorted `LIMIT`, tombstone deletes, and a whole-table text snapshot. Indexes, joins, a SQL front end, transactions and a WAL are listed as milestones rather than left as hidden gaps. Every call is synchronous on purpose (a spreadsheet repaints in a loop, Ranger has no generics for a `Task<T>`, and `async` is honoured by one class writer), so the asynchronous DuckDB driver is confined to a worker thread with the main thread blocking on `Atomics.wait` and collecting the reply with `receiveMessageOnPort`. One contract suite runs on all of them: `rangerdb` 53/53 + 19 engine-internal tests, `sqlite` (`node:sqlite`) 53/53, `duckdb` (optional `@duckdb/node-api`) 53/53; RangerDB's 72 also pass from the same source on JavaScript, Python and native C++, and the portable half compiles for 12/12 targets. `GridDbSource` makes a sheet out of a query and an `UPDATE` out of an edited cell — read-only without key columns, a refused value restored rather than left on screen, and sorting/filtering that re-runs the query instead of ordering the loaded page — verified over all three engines by `npm run datagrid:db:test` (93/93). **Ctrl+Q opens a SQL box inside the editor**: what is typed is parsed by RangerSQL and planned into the same `QuerySpec` a sheet built in code uses, so a typed `SELECT` is an ordinary editable database sheet (sort, filter, write-back and formulas all keep working); a statement beyond the planner is handed to the engine as text and the sheet says it is read only, and RangerDB — whose SQL is that planner — refuses it with the reason instead. The same thing is the `db.sql` command, so a host drives it over HTTP, and `datagrid:db:window:smoke` opens the box the way a person does (command, text events, Enter). Two bugs in the new code, found by the suite: binding a spec-driven sheet left the previous sheet's raw SQL in place, so the next sheet re-ran the old query; and `RangerDbBackend.lastError` was never cleared on success, so one refused statement made every later query look like a failure. Host backends now report their primary keys (`pragma_table_info` on SQLite, `duckdb_constraints()` on DuckDB), without which a sheet built from a typed query had no address to write an edit back to. `npm run datagrid:db:window` opens the ordinary WebGL editor window on a live database instead of an .xlsx — the host picks DuckDB if it is installed, SQLite if not and RangerDB if there is no host database at all, seeds a demo table when the one it is pointed at is empty, and `--db-dsn` points it at a real file; `npm run datagrid:db:window:smoke` drives that same host with no browser, posting the click/type/Enter events the browser posts and checking the scene it would have drawn. `npm run rangerdb:bench` times the same queries per engine and the README explains which columns are about the engines and which are about the API's row-at-a-time write path
|
|
2365
3409
|
- **The playground shows shapes, on twelve targets** — the browser bundle always carried every writer the `rgrc` CLI has, but the UI offered three (JavaScript, Kotlin, Swift 6). The target picker now lists **JavaScript/TypeScript, Python, Go, Rust, C++, C#, Java, Kotlin, Swift 6, Dart, PHP and Scala**, each with its own highlighter, and four `shape` / `case` / `group` examples come with it — the closed variant family, exhaustive `match`, methods on a family, and group capabilities — so the point of PLAN_SHAPES.md ("one source, each target's own representation") can be seen by switching the dropdown: a tagged object on JavaScript and Python, a native `enum` on Rust and Swift, an interface on Kotlin and C#, a variant on C++. LLVM is left out because it has no lowering for shapes (`case` over a shape case fails to match argument types), and Swift 3 because the Swift 6 writer supersedes it. Two things the wider target list exposed: **Java output was empty in the playground** — the Java writer leaves the requested output file empty and writes one file per class beside it, and the reader returned that empty exact match rather than the code; it now shows every file that has content, each under its own banner. And **Scala cannot compile `RangerProcess.rgr`** at all (a `for`-loop with `continue`), so the five process examples mark it unsupported and the picker greys it out with the reason on hover instead of dropping compiler errors into the output pane. The example and target are both in the URL now (`?example=shape-value&lang=rust`)
|
|
@@ -2539,7 +3583,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2539
3583
|
### Known gaps
|
|
2540
3584
|
|
|
2541
3585
|
- **`@serialize(true)` does not work for `cpp` or `rust`** — not a template gap: `systemclass JSONDataObject` and `JSONArrayObject` declare no C++ or Rust type at all, so these targets have no JSON representation to serialize into. Needs a design decision, not a template
|
|
2542
|
-
- Unary minus, `range`, `min`, `abs(int)`, `round`, `pow` and `log` remain unimplemented; see [PLAN_OPERATORS.md](
|
|
3586
|
+
- Unary minus, `range`, `min`, `abs(int)`, `round`, `pow` and `log` remain unimplemented; see [PLAN_OPERATORS.md](docs/plans/PLAN_OPERATORS.md) §5
|
|
2543
3587
|
|
|
2544
3588
|
## [3.2.1] - 2026-08-01
|
|
2545
3589
|
|
|
@@ -2608,9 +3652,9 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2608
3652
|
|
|
2609
3653
|
- **`prepublishOnly` no longer runs the whole repository suite** — publishing the compiler ran all 56 test files, including the gallery, game-engine and native-toolchain suites. Those need SDL2, `g++`, Cannon and game fixtures that ship with neither the repo nor the package, so `npm publish` failed on the publisher's machine for reasons unrelated to the compiler (missing `SDL2/SDL.h`, `gallery/game_engine/games/ylos/index.tsx` and `physics_race/index.tsx` are absent from the repository entirely). `prepublishOnly` and `.github/workflows/publish.yml` now run `npm run test:publish` — 44 files, 355 tests, ~90s — covering parsing, type checking and code generation for every target backend. `npm test` still runs everything, and `ci.yml` is unchanged.
|
|
2610
3654
|
|
|
2611
|
-
- **`build:dist` now includes `build:dist:module`** — `dist/api.js` was not rebuilt by the release build, which is how it drifted behind `
|
|
3655
|
+
- **`build:dist` now includes `build:dist:module`** — `dist/api.js` was not rebuilt by the release build, which is how it drifted behind `dist/rgrc.js`
|
|
2612
3656
|
|
|
2613
|
-
- **`dist/api.js` rebuilt from current sources** — the committed programmatic-API bundle predated several compiler fixes, so `require("ranger-compiler")` shipped older behaviour than the `rgrc` CLI. `scripts/patch-chain-desugar.js` now patches `dist/api.js` as well as `
|
|
3657
|
+
- **`dist/api.js` rebuilt from current sources** — the committed programmatic-API bundle predated several compiler fixes, so `require("ranger-compiler")` shipped older behaviour than the `rgrc` CLI. `scripts/patch-chain-desugar.js` now patches `dist/api.js` as well as `dist/rgrc.js`, and `build:dist:module` runs it after `tsc`
|
|
2614
3658
|
|
|
2615
3659
|
## [3.1.1] - 2026-06-23
|
|
2616
3660
|
|
|
@@ -2641,13 +3685,13 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2641
3685
|
|
|
2642
3686
|
### Added
|
|
2643
3687
|
|
|
2644
|
-
- **`ProcessUiHost` notify suppress** — `beginSuppressUiNotify` / `endSuppressUiNotify` / `isUiNotifySuppressed` for batching parent↔child sync without re-entrant UI notify loops ([PROCESS_UI_NOTIFY.md](PROCESS_UI_NOTIFY.md))
|
|
3688
|
+
- **`ProcessUiHost` notify suppress** — `beginSuppressUiNotify` / `endSuppressUiNotify` / `isUiNotifySuppressed` for batching parent↔child sync without re-entrant UI notify loops ([PROCESS_UI_NOTIFY.md](docs/plans/process/PROCESS_UI_NOTIFY.md))
|
|
2645
3689
|
- **Process view DTO regression fixture** — [tests/fixtures/process_view_dto_assign.rgr](tests/fixtures/process_view_dto_assign.rgr) (cross-class field assignment with method call on RHS)
|
|
2646
|
-
- **Docs** — [PROCESS_UI_NOTIFY.md](PROCESS_UI_NOTIFY.md), [PROCESS_UI_VIEW_MODELS.md](PROCESS_UI_VIEW_MODELS.md); README `@process` quick start
|
|
3690
|
+
- **Docs** — [PROCESS_UI_NOTIFY.md](docs/plans/process/PROCESS_UI_NOTIFY.md), [PROCESS_UI_VIEW_MODELS.md](legacy/docs/PROCESS_UI_VIEW_MODELS.md); README `@process` quick start
|
|
2647
3691
|
|
|
2648
3692
|
### Fixed
|
|
2649
3693
|
|
|
2650
|
-
- **Parser: assignment RHS method calls** — `row.field = this.helper(index)` no longer splits the call into invalid `=` operands ([PROCESS_UI_VIEW_MODELS.md](PROCESS_UI_VIEW_MODELS.md)); fix in [compiler/ng_RangerFlowParser.rgr](compiler/ng_RangerFlowParser.rgr) (`repairAssignMethodCallRhs`)
|
|
3694
|
+
- **Parser: assignment RHS method calls** — `row.field = this.helper(index)` no longer splits the call into invalid `=` operands ([PROCESS_UI_VIEW_MODELS.md](legacy/docs/PROCESS_UI_VIEW_MODELS.md)); fix in [compiler/ng_RangerFlowParser.rgr](compiler/ng_RangerFlowParser.rgr) (`repairAssignMethodCallRhs`)
|
|
2651
3695
|
|
|
2652
3696
|
### Changed
|
|
2653
3697
|
|
|
@@ -2719,7 +3763,7 @@ engine, Mermaid, Figma, CodeGraph, Rave — and is not in the npm tarball.
|
|
|
2719
3763
|
- **Kotlin int/int division** — Casts operands with `.toDouble()` to avoid integer truncation (`compiler/Lang.rgr`)
|
|
2720
3764
|
- **Kotlin `open fun` warnings** — `open` modifier now only emitted when a class actually has subclasses (`compiler/ng_RangerKotlinClassWriter.rgr`)
|
|
2721
3765
|
- **TypeScript `instanceof` with structural types** — `typeof` in `case` context no longer emits `instanceof Record<string,any>` for mapped types; collapses to `Object`/`Array` at runtime (`compiler/ng_LiveCompiler.rgr`)
|
|
2722
|
-
- **npm package bin path** — Changed from `
|
|
3766
|
+
- **npm package bin path** — Changed from `dist/rgrc.js` to `dist/rgrc.js` so the published package contains a valid binary
|
|
2723
3767
|
|
|
2724
3768
|
### Changed
|
|
2725
3769
|
|