SPINE
Spine is a prototype of a software architecture that enables direct manipulation while remaining live and editable. It’s a declarative push-based reactive system, enabling incremental computation.
It’s incomplete, buggy and poorly written.
(Presentation for LIVE 2025 with additional examples)
Examples
Spine supports multi-touch.
Note: Not not all examples are well-behaved, and might start breaking when you edit. You might be able to refresh the browser to continue.
Examples
- Broccoli (embedded above, but with assymetry option)
- Dot Spiral (embedded above)
- Binary Tree with state (Recursion)
- Worm Game (Play using arrow keys)
- Sine Dots
Examples from LIVE 2025 presentation
- Clamp
- Round
- Rewire on control key
- Mix and smooth on control key
- Line using mix
- Mix Reversal Plot (Graph plot in Mad Tea Lab)
- Rectangle Gizmo
- Integrate - Move point with arrow keys
- Minimal Game
- Slider
- Color picker
Other examples:
- three.js wrapper (uses three js version from 2015)
- Binary Tree, position offsets in array (Recursion)
- Movement Interaction - using multi-touch, a feedback loop can be created
- Pin to re-balance movement
- For is just a component
- Line using mix inluding outside 0 - 1
- Structural changes
- Bidirectional smooth / filter
- Chained smoothing
What and Why
(Simplified glossary of the fancy terms)
- Declarative - so we don’t have to manage objects
- Reactive - so we don’t have to manage updates
- Bidirectional - so we can define interactive behaviour in a minimal way
- Direct Manipulation - so interaction can automatically change the program
- Incremental Computation - so that previously computed values are automatically cached and re-used
- Incremental Code Update - so that programs can remain live editable as they grow (in Spine, each line is a compilation unit)



Parser
The parser is inspired by JSON and designed to have a generic and loose syntax. It leaves validation / interpretation up to the next abstraction level. Like in CoffeeScript, argument parenthesis can be omitted. Like Lua, there is a single a container structure, supporting order and optional keys. Brackets () [] {}, all have the same behaviour, except () for items <= 1, which are special to support infix notation etc.
Comparison: Lua / js vs Spine
| Lua / js | Spine impl | |
|---|---|---|
| Value | untyped value |
reactiveValue untyped value wrapped in reactive container create() get() set() |
| Combined Array / Dict |
table / object*{}.insert() / .push()*- / .map()* |
reactiveArrayraCreate()raInsert()raMap() |
| Parse | load() / new Functor() |
parseToAST() |
| “Runnable” | function | AST (js object) |
| “Run” | () | instantiateRec()returns live instance handle AND reactiveValue |
| “State” | Call Stack Contains local data for currently running function calls |
Block Tree Contains everything that is instantiated |
* js don't have a combined object/array
Source Code
Source needs serious overhaul or a complete rewrite.
Key parts:
- Operators
- Components - inside createSpineApp()
- if component
- point component
- Reactive Value - create, get, set
- Reactive Array - raCreate, raInsert etc
- instantiateRec - traverse AST to instantiate
- parseToAST - precedence table
- unparseExpression - reverse parsing
- Demo “main”
Spine was written in a weird transpiler language, however it outputs somewhat readable js. So, if you prefer, you could read the transpiled code, search for '*' or spineComp_if or similar. You can run the transpiler environment live (development mode)
I’ve started rewriting parts of the code in C# for my demoscene projects, however I did not really do much bidirectional stuff yet.
Options
- You can use
debug=1in the url query string to see some internals (Block tree and ASTs) - When running in development mode, use
log=spineto turn on verbose logging incremental=0turns off incremental code updates (all objects will be recreated when you change the code)- Using
embed=1makes it so that clicking the icon opens a new window to edit to code. - With
hidecode=1, code will not be accessible fontsize=8set fontsize of the code
Missing / Unsolved
- No
else/else ifsupport - No key-value pair iteration
- Empty lines break namespace find (each line needs actual expression)
- Lines with uneven indentation are silently ignored
- Long lines cannot be re-formatted using linefeeds (by design!), solution: a component
sublinesToArray(), that syncs code sublines to a reactive array (converting any assignment operators to keys). Ideally with some syntax sugar like this
- State-component only support numbers, and there is no implementation for “managed app-state”.
- Unary operators don’t support reverse operation
- Random drawing order of graphics
- Drawing order don’t match input hit/picking priority
- Only circle and rect support interaction (point/box are aliases)
- Components cannot detect “missing arguments”, as they are the same as async delays (which are expected)
- Abstraction using
def()does not support default values - Many functions (mix, clamp etc) do not work with reactive arrays
- Making some functions bidirectional by default (round, clamp etc) can lead to bugs/confusion
- Array bindings in combination with operators can incorrectly disconnect from source
- Recursion vs return vs local variables issue
- Parser: additional end-bracket should not parse
- Parser: multiple statements using comma should not parse (multiple statements are supported using semicolon)
- Parser: ’ quotes normalize to " when reverse parsing, maybe not that important?
- Smooth and integrate always process every frame, they should ideally stop per-frame-processing when in stable state (for integrate, when input is 0, for smooth, when input is the same as output)
- While drag action can not be absorbed (points showing a transparent line), the system should force-processing input every frame, instead of only when drag is actually moved.
- Probably many severe namespace bugs (especially in combination with incremental build)
- Code contains many unfinished features and random notes
Random notes/questions:
Architecture is overengineered (for instance, it’s designed with future multi-user support in mind). The database and the Reactive-Text-Tree-API could probably be replaced with the Reactive Array (that is already needed for reactive expressions). However, I would still prefer to have separate systems for debugging purposes at this point…
Reactive propagation order glitches?
- timestamp metadata per reactive write, together with a deferring scheme, might help, but maybe also too complex
Execution order issues related to deferred events etc?
- General: Introduce priority within
later()/flushLater(), or some kind of transaction-scope helper with a complete-callback - Alternatively, make direct manipulation special, having global control of the update should make it possible to craft better behaviour
- General: Introduce priority within
Commonplace methods like polynomials etc, will not be reversible without AST/symbol analysis. Even if doable, not sure it’s worth the added complexity, I suspect designing more specific higher level components is better.
It’s a struggle to make ok API’s/adapters for interfacing with the language (except for simpler number funcs)
Making the adapter for three.js was surprisingly hard (possibly due to unstable nature of the system at the time)
- how can we make general adapters for porting js libraries to declarative?