rupaka (रूपक) means a form, a play that the audience sees, and the figure of speech that shows one thing through another. A rupaka demo is one interactive figure that teaches one idea. The learner changes the inputs and sees the result change.
A demo never shows the answer first. Each demo follows one loop:
- Predict. The demo shows the inputs and asks one question. The result is hidden.
- Reveal. The learner locks in with ⌘↩. The demo shows the result and says if the prediction was right.
- Manipulate. Now the controls are live. The result changes when the inputs change.
- Explain. The caption ends with a new question that the learner answers with the controls.
The validator enforces step 1: a block that shows a value which the answer depends on must wait for the gate.
A demo is a JSON spec, not code. It has a small state, derived values from named functions (fns), and a list of blocks. A model can write a spec safely, because a spec cannot run code.
| part | what it is |
|---|---|
| block | a reusable UI part in rupaka/blocks/<name>/: its JS, its CSS, a block.json manifest and an example |
| fn | a pure function that computes a derived value: rules, rrf, softmax, lookup, expr, ids, pick, sort, fmt |
| gate | the block that asks for the prediction and hides the result until the lock in |
| spec | the JSON object: state, derive, blocks and tables |
The blocks are gate, readout, caption, row, stack, slider, toggle, choice, wheel, ranklist, bars, axis, point, plot and grid.
pip install rupakaThe headless check needs playwright: pip install "rupaka[check]". It uses the Chromium that playwright install chromium gives, or the browser in $FASTCDP_CHROME. It never runs your everyday Chrome.
Build a spec with the builders, or write the JSON.
from rupaka.spec import *d = demo('sum', 'A sum', ask='Predict the sum before you see it.', state=dict(a=2, b=3), derive=[expr('a + b', 'sum')],
blocks=[gate('s', 'What is {a} + {b}?', [4, 5, 6], 'sum', why='Say why, in one line.'),
readout('{a} + {b} = {sum}', after='s')])validate returns the errors. An empty list means the spec is good.
assert validate(d) == []A readout that shows the sum before the gate breaks the think rule:
bad = {**d, 'blocks': [d['blocks'][0], readout('{a} + {b} = {sum}')]}
validate(bad)Then put the demo where the learner is:
| where | how |
|---|---|
| a notebook (leela, Jupyter) | show(spec): an HTML output in a sandboxed frame, and the application/vnd.rupaka+json mime |
| Markdown | md(spec) writes a ```rupaka fence; md(spec, frame=True) writes a frame that leela draws today |
| a FastHTML page | Demo(spec) and Assets('/rupaka'); serve bundle('js') and bundle('css') at that prefix |
| a file | save(spec, 'demo.html') writes one page that works from file:// with no network |
check(spec) draws the demo in headless Chrome and plays it. author(concept, ask) asks a model for a demo, and checks and repairs it.
rupaka/demos/ holds hand-made demos: vowel sandhi, reciprocal rank fusion, softmax and temperature, nakṣatra and pāda, and a parabola.
The notebooks in nbs/ are the source of the Python. The JS and CSS are hand-written files under rupaka/static/, rupaka/blocks/ and rupaka/fns/.
uv sync
uv run nbdev-prepare
uv run pytest