Skip to content

How the engine works ​

SayShape's rendering pipelineSpec text is parsed into a tree, laid out into boxes and painted onto the canvas. Pointer and keyboard input on the canvas runs actions, which change state, which causes a new layout.Spec textlines of SayShapeParsetree + diagnosticsLayouta box per elementPaintcanvas callsCanvaspixelsStateActionson=, bind=pointer, keysre-layout
Each stage reruns only when its input changes: editing the spec re-parses, a state change re-lays out, and hovering only repaints.
StageFileWhat it does
Parsesrc/parser.tsText to a SayShapeNode tree plus line-numbered diagnostics, validated against the component definitions
Expressionssrc/expr.tsIts own tokenizer, parser and evaluator for {…}, with a fixed list of functions
Derived valuessrc/state.tsderive values, worked out lazily and cached until something they read changes
Actionssrc/actions.tson= strings: inc, dec, toggle, set, emit, go, back, notify, add, remove, clear
Datasrc/data.tsLists and records: frozen, with no prototype; host data is copied in
Listssrc/each.tseach copies, each item's scope, and where it lives in state for writing
Layoutsrc/layout.tsHeadless: needs only a text measurer, and produces a box per element
Enginesrc/engine.tsThe SayShape class: the staged pipeline, canvas sizing, painting, hit testing, pointer and keyboard
Componentssrc/components/One ComponentDef per component; the map is a separate entry point
Popupssrc/popup.tsOne popup at a time (a select's list, a date picker's calendar), painted last and hit-tested first
Themesrc/theme.tsDesign tokens, light and dark, --sayshape-* overrides
Model guidesrc/prompt.ts, src/profiles.tsThe guide generated from components, in full and compact modes, with profiles
Model clientsrc/model/sayshape/model: streaming, cleaning, checking and one repair
Testingsrc/testing/sayshape/testing: the real engine in Node against a recording canvas

Staged rendering ​

The engine tracks which stage is out of date: parse, layout or paint. A spec edit re-parses; a state change re-lays out; hovering or a focus ring only repaints. Frames are scheduled with requestAnimationFrame, and the render event reports how long each stage took. The playground shows those timings for the current spec.

Components ​

A component is one ComponentDef: identity and documentation (type, category, description, props, children, parents, example), layout(box, ctx) to set its height and place children, paint and paintOver to draw with theme tokens, and optionally press, drag, key, hitTest, editor and popup. Registering it is all it takes: the parser validates its props, the guide describes it, the reference on this site documents it, and the engine lays it out and paints it. See Adding a component.

Text input ​

Text fields are drawn on the canvas like everything else. While someone types in one, the engine places a real <input> exactly over it, with the field's font and colors, so the browser handles typing, selection, copy and paste, undo, input methods, autocorrect, password managers and the phone keyboard. Every keystroke writes the field's bind=. The <input> is removed when they leave the field.

Popups ​

A select shows its options, and a date-picker its calendar, in a popup the engine draws above everything else. Only one is open at a time; a press outside closes it without pressing what's underneath; keys go to it while it's open. Because a canvas can't draw outside itself, the canvas grows while a long list is open and shrinks back afterwards.

Debugging ​

Browser developer tools can't see inside a canvas, so SayShape has its own. All of them are in the playground, and available from code:

ToolHowWhat it shows
Layout boxesui.inspector.setBoxes(true)Every box, with its type and size
Hit regionsui.inspector.setHits(true)Exactly what is clickable
Inspect modeui.inspector.setInspecting(true)Hover to highlight, click to select; selectLine(n) selects the element on a spec line
Describeui.inspector.describe(node)A node's type, line, props and box
Frame captureawait ui.inspector.captureFrame()Every canvas call made in one frame
Consoleui.expose()Puts the instance on window.sayshape

To find a bug, decide which stage it's in: an element missing or wrong is the parse (check ui.diagnostics); a wrong position or size is layout (turn on boxes); a right box that looks wrong is paint (capture a frame); a click that does nothing is input (turn on hit regions).

SayShape v0.1 is an early prototype under the MIT License. Status · GitHub