kestrel-loomv0.1.0

Architecture

Why the core never touches a browser API, and how that makes a full frame testable.

Three layers

crates/renderer/   kestrel-loom        state, viewport, scales, panes, tools,
                                       render loop, RenderCommand model — no browser API
crates/wasm/       kestrel-loom-wasm   wasm-bindgen façade + Canvas 2D execution
js/                kestrel-loom.js     grouped JavaScript wrapper — what you embed
Focus
From your page to the canvas
From your page to the canvasYour page hands candles and input to the JavaScript wrapper. Through the WebAssembly façade they reach the Rust core, which keeps chart state, viewport and tools and takes its indicators from kestrel-chartkit. A render pass produces drawing commands, and the WASM crate executes them on a Canvas 2D context.Your page → kestrel-loom.jskestrel-loom.js → kestrel-loom: WASM façadekestrel-chartkit → kestrel-loom: on_barkestrel-loom → kestrel-loom-wasm: RenderCommandskestrel-loom-wasm → <canvas>WASM façadeon_barRenderCommandsYour page – candles, mouse, touchYour pagecandles, mouse, touchkestrel-loom.js – wrapper you embedkestrel-loom.jswrapper you embedkestrel-loom – Rust core, no browser APIkestrel-loomRust core, no browser APIkestrel-chartkit – indicator mathkestrel-chartkitindicator mathkestrel-loom-wasm – Canvas 2D executionkestrel-loom-wasmCanvas 2D execution<canvas> – the chart<canvas>the chart

Source: README.md (Layout), crates/renderer, crates/wasm, js/kestrel-loom.js

Show chart data
Data for From your page to the canvas
StepKindLeads to
Your pageinput or outputkestrel-loom.js
kestrel-loom.jsstepkestrel-loom (WASM façade)
kestrel-chartkitexternalkestrel-loom (on_bar)
kestrel-loomstepkestrel-loom-wasm (RenderCommands)
kestrel-loom-wasmstep<canvas>
<canvas>input or output
Focus
From your page to the canvas
From your page to the canvasYour page hands candles and input to the JavaScript wrapper. Through the WebAssembly façade they reach the Rust core, which keeps chart state, viewport and tools and takes its indicators from kestrel-chartkit. A render pass produces drawing commands, and the WASM crate executes them on a Canvas 2D context.Your page → kestrel-loom.jskestrel-loom.js → kestrel-loom: WASM façadekestrel-chartkit → kestrel-loom: on_barkestrel-loom → kestrel-loom-wasm: RenderCommandskestrel-loom-wasm → <canvas>WASM façadeon_barRenderCommandsYour page – candles, mouse, touchYour pagecandles, mouse, touchkestrel-loom.js – wrapper you embedkestrel-loom.jswrapper you embedkestrel-loom – Rust core, no browser APIkestrel-loomRust core, no browser APIkestrel-chartkit – indicator mathkestrel-chartkitindicator mathkestrel-loom-wasm – Canvas 2D executionkestrel-loom-wasmCanvas 2D execution<canvas> – the chart<canvas>the chart

Source: README.md (Layout), crates/renderer, crates/wasm, js/kestrel-loom.js

Show chart data
Data for From your page to the canvas
StepKindLeads to
Your pageinput or outputkestrel-loom.js
kestrel-loom.jsstepkestrel-loom (WASM façade)
kestrel-chartkitexternalkestrel-loom (on_bar)
kestrel-loomstepkestrel-loom-wasm (RenderCommands)
kestrel-loom-wasmstep<canvas>
<canvas>input or output

A render pass in the core writes RenderCommands — lines, rectangles, text, candles, indicator lines — into a renderer. The WASM crate executes them on a CanvasRenderingContext2d; in tests, a recorder collects them instead.

Browser-free, enforced

CI fails if cargo tree -p kestrel-loom ever contains web-sys, js-sys or wasm-bindgen. That rule is what keeps a whole frame testable without a browser.

Golden command streams

The core’s golden tests render deterministic scenes — fixed seed, fixed size, fixed time range — and compare the resulting command stream with a committed fixture in crates/renderer/tests/fixtures/:

full_frame_candlestick.txt
full_frame_with_bollinger_overlay.txt
full_frame_with_rsi_pane.txt
tools_trend_and_level.txt

The start of the frame with an RSI pane: the canvas is cleared, the grid drawn, then each candle follows as a wick line and a body rectangle, all with exact coordinates.

crates/renderer/tests/fixtures/full_frame_with_rsi_pane.txtfirst 24 of 338 commands
clear
line (0.00,288.00)-(736.00,288.00) w=1.00
line (0.00,246.86)-(736.00,246.86) w=1.00
line (0.00,205.71)-(736.00,205.71) w=1.00
line (0.00,164.57)-(736.00,164.57) w=1.00
line (0.00,123.43)-(736.00,123.43) w=1.00
line (0.00,82.29)-(736.00,82.29) w=1.00
line (0.00,41.14)-(736.00,41.14) w=1.00
line (0.00,0.00)-(736.00,0.00) w=1.00
line (173.85,0.00)-(173.85,288.00) w=1.00
line (334.53,0.00)-(334.53,288.00) w=1.00
line (495.20,0.00)-(495.20,288.00) w=1.00
line (655.88,0.00)-(655.88,288.00) w=1.00
line (36.98,62.83)-(36.98,95.49) w=1.00
rect (35.48,66.42) 3.00x15.87 fill=true stroke=false w=0.00
line (41.45,63.71)-(41.45,67.28) w=1.00
rect (39.95,64.70) 3.00x1.72 fill=true stroke=false w=0.00
line (45.91,52.18)-(45.91,90.56) w=1.00
rect (44.41,64.70) 3.00x12.62 fill=true stroke=false w=0.00
line (50.37,73.06)-(50.37,88.59) w=1.00
rect (48.87,77.32) 3.00x6.06 fill=true stroke=false w=0.00
line (54.84,79.14)-(54.84,97.72) w=1.00
rect (53.34,83.38) 3.00x8.57 fill=true stroke=false w=0.00
line (59.30,89.46)-(59.30,98.74) w=1.00

A change in drawing behaviour fails the test even when the result still looks plausible in a browser. The fixtures are rewritten only on purpose:

UPDATE_GOLDEN=1 cargo test -p kestrel-loom --test golden_render_commands

The split with kestrel-chartkit

kestrel-chartkit indicator math, scoring, artifacts, the renderer-neutral scene model
kestrel-loom viewport, scales, panes, tools, the render loop, canvas output

kestrel-chartkit knows nothing about browsers; kestrel-loom knows nothing about how an RSI is computed. They meet at a data contract, and kestrel-loom pins kestrel-chartkit to a release tag.

Edit this page on GitHub · Docs for v0.1.0