The VectoJS Paradigm
Abandon the HTML/CSS mental model before touching a VectoJS project. VectoJS is
not "a canvas inside a webpage" — the canvas is the page. There is one
<canvas>, one Scene, and a retained scene graph (the Virtual Math Tree) of
Entity objects whose numeric state fully determines every pixel. If you find
yourself reaching for a <div>, a stylesheet, or a screenshot, stop and
re-enter the paradigm.
The translation table (habit → VectoJS)
| HTML/CSS habit |
VectoJS way |
|
|
|
Add a <div>/<span> wrapper |
Add an Entity (or Stack/Flow/Card container) to the scene tree |
|
|
|
| Write CSS rules / classes |
Set entity properties: x, y, width, scaleX, opacity, colors on the component |
|
|
|
| Flexbox / grid layout |
Stack (vertical/horizontal + gap) and Flow (wrapping); positions are numbers you own |
|
|
|
| Media queries |
Breakpoint functions on scene.width/scene.height (logical px); reposition and markDirty() |
|
|
|
| CSS transition / keyframes |
setTransition({ x: 'spring' }) then assign, or animateTo/springTo (promise-based) |
|
|
|
z-index |
Tree order (later siblings draw on top) or scene.showOverlay() |
|
|
|
document.querySelector / DOM events |
Keep references to your entities; `entity.on('click' \ |
'hover' \ |
'wheel' \ |
'keydown', …)` |
:hover styles |
entity.on('hover') / on('pointerleave') mutate state, scene repaints |
|
|
|
overflow: scroll |
ScrollView / VirtualList / TreeView / virtualized Table (one owner per region) |
|
|
|
<input>, IME, clipboard |
@vectojs/ui Input/TextArea — the ONE place a real DOM element is correct |
|
|
|
| Accessibility via ARIA markup |
getA11yAttributes() on the entity; the Scene projects the semantic node for you |
|
|
|
| SEO/find-in-page via HTML text |
getContentProjection() (core 0.2.7+) mirrors canvas text into the DOM automatically |
|
|
|
Never hand-author sibling DOM next to the canvas for layout, styling, or
events. The only DOM VectoJS wants is the DOM it projects itself.
Debug in state space, not pixel space
This is the paradigm's biggest efficiency win: every visual fact has a
numeric cause you can read in code. A misplaced button is not "somewhere on
the screenshot" — it is an entity whose x/y/width/getWorldTransform()
you can print and assert. Work in this order:
- Read the tree.
scene.getA11yTree() returns the semantic snapshot
(roles, labels, values, hierarchy). Wrong structure here = wrong structure
on screen, found without rendering anything. It reports what is projected:
an entity is absent unless interactive and it has a box (or
a11yFullViewport), so an empty or short tree is often a missing opt-in
rather than a structural bug.
- Read the numbers.
entity.getWorldTransform(), .width, .opacity,
hasPendingAnimations(). Layout bugs are arithmetic bugs — compare the
number you got with the number you expected. @vectojs/devtools (0.1.0+)
packages this rung: pickInScene(scene, x, y) names the entity that owns a
point and describeEntity(hit) prints its geometry — see the
vectojs-devtools skill.
- Reproduce deterministically.
scene.step(16.67) advances exactly one
frame with no wall clock; a "flickers sometimes" bug becomes "wrong at
frame N" — steppable in a unit test.
- Snapshot as vectors, not pixels.
scene.toSVG() serializes the frame
as inspectable XML — diff two SVGs to see which shape moved by how much,
instead of eyeballing two PNGs.
- Drive it by role. Playwright/agents click
getByRole('button', …) on
the projected a11y layer — behavioral assertions without coordinates.
- Screenshot last. A screenshot is for confirming a fix or catching
what you cannot model (font rasterization, GPU compositing, DPR artifacts —
and test those at
deviceScaleFactor: 2). It is never the first probe.
Red flags — stop and re-enter the paradigm
- "I'll just add a div/absolutely-positioned element over the canvas."
- "Let me write some CSS for this." (The canvas has no CSS. Entity properties.)
- "Take a screenshot and look for the problem." (Read
getA11yTree() and the
entity's numbers first — the bug is a value, not a picture.)
- "querySelector / addEventListener on the document." (Entities own events.)
- "It needs
position: fixed." (That's showOverlay().)
- "I'll poll with setTimeout until it looks right." (Use
step(dt) and assert.)
Cross-references
Build/runtime contracts → vectojs-core-runtime · layout →
vectojs-responsive-layout · motion → vectojs-ui-animation · speed →
vectojs-performance · 3D/XR → vectojs-three · MP4 export →
vectojs-video-exporter.
A declarative style layer (@vectojs/styles) is planned on top of the numeric
VMT — typed style objects + applyStyle() mapped to entity fields, never a
CSS string parser (a parser would reintroduce the cascade/global state the
numeric VMT exists to remove). Until it ships, styling stays numeric entity
fields; the token-layer precedent lives in markdown/src/theme.ts.
1---2name: vectojs-paradigm3description: Use FIRST when building, changing, or debugging any UI in a VectoJS project — before writing code, before taking screenshots, and especially when tempted to add HTML elements, write CSS, use querySelector, or "look at" a rendering problem visually.4---56# The VectoJS Paradigm78Abandon the HTML/CSS mental model before touching a VectoJS project. VectoJS is9not "a canvas inside a webpage" — the canvas **is** the page. There is one10`<canvas>`, one `Scene`, and a retained scene graph (the Virtual Math Tree) of11`Entity` objects whose numeric state fully determines every pixel. If you find12yourself reaching for a `<div>`, a stylesheet, or a screenshot, stop and13re-enter the paradigm.1415## The translation table (habit → VectoJS)1617| HTML/CSS habit | VectoJS way | | | |18| ------------------------------------- | ----------------------------------------------------------------------------------------------- | --------- | --------- | -------------- |19| Add a `<div>`/`<span>` wrapper | Add an `Entity` (or `Stack`/`Flow`/`Card` container) to the scene tree | | | |20| Write CSS rules / classes | Set entity properties: `x`, `y`, `width`, `scaleX`, `opacity`, colors on the component | | | |21| Flexbox / grid layout | `Stack` (vertical/horizontal + gap) and `Flow` (wrapping); positions are numbers you own | | | |22| Media queries | Breakpoint functions on `scene.width`/`scene.height` (logical px); reposition and `markDirty()` | | | |23| CSS transition / keyframes | `setTransition({ x: 'spring' })` then assign, or `animateTo`/`springTo` (promise-based) | | | |24| `z-index` | Tree order (later siblings draw on top) or `scene.showOverlay()` | | | |25| `document.querySelector` / DOM events | Keep references to your entities; `entity.on('click' \ | 'hover' \ | 'wheel' \ | 'keydown', …)` |26| `:hover` styles | `entity.on('hover')` / `on('pointerleave')` mutate state, scene repaints | | | |27| `overflow: scroll` | `ScrollView` / `VirtualList` / `TreeView` / virtualized `Table` (one owner per region) | | | |28| `<input>`, IME, clipboard | `@vectojs/ui` `Input`/`TextArea` — the ONE place a real DOM element is correct | | | |29| Accessibility via ARIA markup | `getA11yAttributes()` on the entity; the Scene projects the semantic node for you | | | |30| SEO/find-in-page via HTML text | `getContentProjection()` (core 0.2.7+) mirrors canvas text into the DOM automatically | | | |3132**Never** hand-author sibling DOM next to the canvas for layout, styling, or33events. The only DOM VectoJS wants is the DOM it projects itself.3435## Debug in state space, not pixel space3637This is the paradigm's biggest efficiency win: **every visual fact has a38numeric cause you can read in code.** A misplaced button is not "somewhere on39the screenshot" — it is an entity whose `x`/`y`/`width`/`getWorldTransform()`40you can print and assert. Work in this order:41421. **Read the tree.** `scene.getA11yTree()` returns the semantic snapshot43 (roles, labels, values, hierarchy). Wrong structure here = wrong structure44 on screen, found without rendering anything. It reports what is _projected_:45 an entity is absent unless `interactive` and it has a box (or46 `a11yFullViewport`), so an empty or short tree is often a missing opt-in47 rather than a structural bug.482. **Read the numbers.** `entity.getWorldTransform()`, `.width`, `.opacity`,49 `hasPendingAnimations()`. Layout bugs are arithmetic bugs — compare the50 number you got with the number you expected. `@vectojs/devtools` (0.1.0+)51 packages this rung: `pickInScene(scene, x, y)` names the entity that owns a52 point and `describeEntity(hit)` prints its geometry — see the53 `vectojs-devtools` skill.543. **Reproduce deterministically.** `scene.step(16.67)` advances exactly one55 frame with no wall clock; a "flickers sometimes" bug becomes "wrong at56 frame N" — steppable in a unit test.574. **Snapshot as vectors, not pixels.** `scene.toSVG()` serializes the frame58 as inspectable XML — diff two SVGs to see _which shape moved by how much_,59 instead of eyeballing two PNGs.605. **Drive it by role.** Playwright/agents click `getByRole('button', …)` on61 the projected a11y layer — behavioral assertions without coordinates.626. **Screenshot last.** A screenshot is for _confirming_ a fix or catching63 what you cannot model (font rasterization, GPU compositing, DPR artifacts —64 and test those at `deviceScaleFactor: 2`). It is never the first probe.6566## Red flags — stop and re-enter the paradigm6768- "I'll just add a div/absolutely-positioned element over the canvas."69- "Let me write some CSS for this." (The canvas has no CSS. Entity properties.)70- "Take a screenshot and look for the problem." (Read `getA11yTree()` and the71 entity's numbers first — the bug is a value, not a picture.)72- "querySelector / addEventListener on the document." (Entities own events.)73- "It needs `position: fixed`." (That's `showOverlay()`.)74- "I'll poll with setTimeout until it looks right." (Use `step(dt)` and assert.)7576## Cross-references7778Build/runtime contracts → **vectojs-core-runtime** · layout →79**vectojs-responsive-layout** · motion → **vectojs-ui-animation** · speed →80**vectojs-performance** · 3D/XR → **vectojs-three** · MP4 export →81**vectojs-video-exporter**.8283A declarative style layer (`@vectojs/styles`) is planned on top of the numeric84VMT — typed style objects + `applyStyle()` mapped to entity fields, **never** a85CSS string parser (a parser would reintroduce the cascade/global state the86numeric VMT exists to remove). Until it ships, styling stays numeric entity87fields; the token-layer precedent lives in `markdown/src/theme.ts`.