Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ controller pattern; `Map<key, rerenderer(view)>` per-row model). Legacy

Open:
- **Rerenderer naming bikeshed** (Rerender vs Rerenderer) — deferred.
- **AnchorBlock** cut candidate; **Toggle** disposition — decide.
- **VirtualList** (recycled-pool / virtualization, FUTURE, separate block) —
`docs/design/VirtualList.md` + `frame-primitives.md`.
- **`stopRerenderer`** (name TBD): opt a subtree out of reuse (mint fresh nodes)
Expand Down
2 changes: 1 addition & 1 deletion docs/design/VirtualList.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# VirtualList (name TBD) — recycled-pool / virtualized list

> Status: FUTURE, separate block. Notes parked while designing
> [KeyedList](../../packages/maya/blocks/KeyedList.js). Not started.
> [KeyedList](../../packages/maya/lists/KeyedList.js). Not started.

The second list strategy from the 2026-06-17 TODO decision. Distinguished
from `KeyedList` by **node lifecycle**, not by a config flag — so it's a
Expand Down
4 changes: 2 additions & 2 deletions packages/azoth/maya.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from '@azothjs/maya/compose';
export * from '@azothjs/maya/renderer';
export * from '@azothjs/maya/blocks';
export * from '@azothjs/maya/lists';
export * from '@azothjs/maya/renderer';
8 changes: 4 additions & 4 deletions packages/azoth/runtime.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export {
compose as __c,
createComponent as __rC,
composeComponent as __cC,
compose as __compose,
createComponent as __createComponent,
composeComponent as __composeComponent,
} from '@azothjs/maya/compose';

export {
renderer as __renderer,
render as __render,
} from '@azothjs/maya/renderer';
21 changes: 0 additions & 21 deletions packages/maya/blocks/AnchorBlock.js

This file was deleted.

3 changes: 0 additions & 3 deletions packages/maya/blocks/Toggle.js

This file was deleted.

6 changes: 3 additions & 3 deletions packages/maya/compose/compose.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Channel } from '../channels/channel.js';
import { activeRerenderer } from '../renderer/rerenderer.js';
import { activeRenderer } from '../renderer/rerenderer.js';

export const IGNORE = Symbol.for('azoth.compose.IGNORE');

Expand All @@ -12,7 +12,7 @@ export function compose(anchor, input, keepLast, props, childNodes) {
// only on the replace path: keepLast=true means accumulate, where
// a repeated value is a legitimate "add another."
if(!keepLast) {
const rr = activeRerenderer();
const rr = activeRenderer();
if(rr && rr.skipIfSame(anchor, input)) return;
}

Expand Down Expand Up @@ -113,7 +113,7 @@ export function compose(anchor, input, keepLast, props, childNodes) {
}

export function composeComponent(anchor, [Constructor, props, childNodes]) {
const rr = activeRerenderer();
const rr = activeRenderer();
if(rr) {
// The update verb. Same Constructor at this anchor → update in
// place. Different Constructor → fall through to create (=== fails
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './Toggle.js';
export * from './KeyedList.js';
export * from './KeyedUList.js';
export * from './KeyedOList.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/maya/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"**/!(*test|*config).js"
],
"exports": {
"./blocks": "./blocks/blocks.js",
"./channels": "./channels/index.js",
"./compose": "./compose/compose.js",
"./renderer": "./renderer/renderer.js"
"./lists": "./lists/index.js",
"./renderer": "./renderer/render.js"
},
"devDependencies": {
"test-utils": "workspace:^"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DOMRenderer } from './dom-renderer.js';
import { HTMLRenderer } from './html-renderer.js';
import { activeRerenderer } from './rerenderer.js';
import { activeRenderer } from './rerenderer.js';

export { rerenderer, activeRerenderer } from './rerenderer.js';
export { rerenderer, activeRenderer } from './rerenderer.js';

const templates = new Map(); // cache
let renderEngine = DOMRenderer; // DOM or HTML engine
Expand All @@ -25,10 +25,10 @@ function get(id, isFragment = false, content) {
return template;
}

export function renderer(id, targets, makeBind, isFragment, content) {
export function render(id, targets, makeBind, isFragment, content) {
const create = get(id, isFragment, content);

// Per-declaration identity: the rerenderer's cache key. Each renderer()
// Per-declaration identity: the rerenderer's cache key. Each render()
// call is one compiled call site (per-site factory) — closure identity IS
// the key, so deduped templates (same id, multiple sites) cannot collide
// across sites.
Expand All @@ -43,7 +43,7 @@ export function renderer(id, targets, makeBind, isFragment, content) {

return (...args) => {
if(!create) return null;
const rr = activeRerenderer();
const rr = activeRenderer();
// Under a rerenderer: reuse this site's cached node, rebound. Otherwise
// (plain forward render): a fresh build.
const { node: root, bind } = rr
Expand Down
2 changes: 1 addition & 1 deletion packages/maya/renderer/rerenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

const stack = [];

export function activeRerenderer() {
export function activeRenderer() {
return stack.length ? stack[stack.length - 1] : null;
}

Expand Down
24 changes: 12 additions & 12 deletions packages/maya/renderer/rerenderer.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, test, beforeAll } from 'vitest';
import { compose, composeComponent, createComponent } from '../compose/compose.js';
import { Channel } from '../channels/channel.js';
import { renderer, RenderService } from './renderer.js';
import { render, RenderService } from './render.js';
import { rerenderer } from './rerenderer.js';

/**
* Increment (a) of the Rerenderer spike — runtime only, intrinsic only.
* Templates are hand-built renderer(...) calls (what thoth emits), per
* Templates are hand-built render(...) calls (what thoth emits), per
* the pattern in controller.test.js. See docs/design/rerenderer.md.
*/

Expand All @@ -18,7 +18,7 @@ const slotBind = targets => {
const t0 = targets[0];
return v0 => compose(t0, v0);
};
const makeP = id => renderer(id, slotTargets, slotBind, false, `<p data-bind><!--0--></p>`);
const makeP = id => render(id, slotTargets, slotBind, false, `<p data-bind><!--0--></p>`);

describe('rerenderer — the gate', () => {

Expand Down Expand Up @@ -65,8 +65,8 @@ describe('rerenderer — control flow (the hooks coin, flipped)', () => {
test('conditional branches: each cached by its own site; sleeping, not dead', ({ expect }) => {
// anchor comment is childNodes[1] here — text "A:" is [0]
const prefixTargets = r => [r.childNodes[1]];
const tA = renderer('rr-br-a', prefixTargets, slotBind, false, `<p data-bind>A:<!--0--></p>`);
const tB = renderer('rr-br-b', prefixTargets, slotBind, false, `<p data-bind>B:<!--0--></p>`);
const tA = render('rr-br-a', prefixTargets, slotBind, false, `<p data-bind>A:<!--0--></p>`);
const tB = render('rr-br-b', prefixTargets, slotBind, false, `<p data-bind>B:<!--0--></p>`);
const fn = rerenderer((flag, v) => flag ? tA(v) : tB(v));

const a1 = fn(true, 'x');
Expand Down Expand Up @@ -107,8 +107,8 @@ describe('rerenderer — control flow (the hooks coin, flipped)', () => {
// Two declarations, SAME template id — the compiled shape after
// per-site factory emission. Closure identity keys the cache.
const html = `<p data-bind><!--0--></p>`;
const tX = renderer('rr-dup', slotTargets, slotBind, false, html);
const tY = renderer('rr-dup', slotTargets, slotBind, false, html);
const tX = render('rr-dup', slotTargets, slotBind, false, html);
const tY = render('rr-dup', slotTargets, slotBind, false, html);
const fn = rerenderer((a, b) => [tX(a), tY(b)]);

const [x1, y1] = fn('left', 'right');
Expand Down Expand Up @@ -168,13 +168,13 @@ describe('rerenderer — anchor memory (=== skip)', () => {
describe('rerenderer — components inside the wrap (increment c)', () => {

// Simulates the compiled component slot: bind receives the tuple
// [Constructor, props] and calls composeComponent (what __cC does).
// [Constructor, props] and calls composeComponent (what __composeComponent does).
const componentBind = targets => {
const t0 = targets[0];
return v0 => composeComponent(t0, v0);
};
const makeHost = id =>
renderer(id, slotTargets, componentBind, false, `<p data-bind><!--0--></p>`);
render(id, slotTargets, componentBind, false, `<p data-bind><!--0--></p>`);

test('chain rule: setup runs once, the returned rerenderable is re-invoked', ({ expect }) => {
const tCard = makeP('rr-c-card');
Expand Down Expand Up @@ -336,11 +336,11 @@ describe('rerenderer — UIComponent protocol (increment d)', () => {
return v0 => composeComponent(t0, v0);
};
const makeHost = id =>
renderer(id, slotTargets, componentBind, false, `<p data-bind><!--0--></p>`);
render(id, slotTargets, componentBind, false, `<p data-bind><!--0--></p>`);

// Transpiled JSX emits one factory declaration per call site (increment
// b). Site identity is the factory's CLOSURE identity (fresh per
// renderer() call), so a render() and an update() that each contain <X/>
// render() call), so a render() and an update() that each contain <X/>
// are distinct sites even with identical HTML — they can't share a node
// the way two calls to one factory would. (That same-HTML-still-distinct
// path is proven by the "deduped templates cannot collide" test above;
Expand Down Expand Up @@ -460,7 +460,7 @@ describe('rerenderer — Channel update verb (increment e)', () => {
return v0 => composeComponent(t0, v0);
};
const makeHost = id =>
renderer(id, slotTargets, channelBind, false, `<p data-bind><!--0--></p>`);
render(id, slotTargets, channelBind, false, `<p data-bind><!--0--></p>`);

// A minimal Observable whose subscribe/unsubscribe we can count.
const observable = () => {
Expand Down
14 changes: 7 additions & 7 deletions packages/thoth/COMPILER.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ The function receives the root element (`r`) and optionally the array of `data-b
const t0 = ts[0], t1 = ts[1], t2 = ts[2];
return (v0, v1, v2) => {
t0.className = v0; // Property assignment
__c(t1, v1); // Compose into child position
__c(t2, v2); // Compose into child position
__compose(t1, v1); // Compose into child position
__compose(t2, v2); // Compose into child position
};
}
```
Expand All @@ -169,16 +169,16 @@ The function receives the root element (`r`) and optionally the array of `data-b
- Property assignment: `t0.className = v0`
- Dataset assignment: `t0.dataset.id = v0`
- Spread props: `Object.assign(t0, v0)`
- Child compose: `__c(t0, v0)` (calls Maya's compose)
- Component compose: `__cC(t0, v0)` (calls Maya's composeComponent)
- Child compose: `__compose(t0, v0)` (calls Maya's compose)
- Component compose: `__composeComponent(t0, v0)` (calls Maya's composeComponent)

### 3. Render Generator (`makeRenderer`)

**Purpose:** Generate the call to Maya's `__renderer` function.
**Purpose:** Generate the call to Maya's `__render` function.

```javascript
// Output
__renderer("77e67b80", g5feceb66, b6b86b273, false, `<p><!--0--></p>`)
__render("77e67b80", g5feceb66, b6b86b273, false, `<p><!--0--></p>`)
```

Arguments:
Expand Down Expand Up @@ -263,7 +263,7 @@ const b = <p>{name2}</p>;
const a = <p>{x}</p>;
const b = <div>{y}</div>;
// Different elements, but same binding pattern (one child compose)
// Can share the same bind function: (ts) => { return (v0) => { __c(ts[0], v0); }; }
// Can share the same bind function: (ts) => { return (v0) => { __compose(ts[0], v0); }; }
```

This emerges naturally from decomposing the compilation into these pieces.
Expand Down
40 changes: 20 additions & 20 deletions packages/thoth/compiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -752,10 +752,10 @@ describe('components and custom element', () => {
const { code, templates } = compile(input);

expect(code).toMatchInlineSnapshot(`
"import { __rC } from 'azoth/runtime';
"import { __createComponent } from 'azoth/runtime';
import { tdb0b7033 } from 'virtual:azoth-templates?id=db0b7033';
const c = __rC(Component, { flag: true, });
const cProps = __rC(Component, { flag: true, other: value, });
const c = __createComponent(Component, { flag: true, });
const cProps = __createComponent(Component, { flag: true, other: value, });
const nested = tdb0b7033([Component, { flag: true, }]);
"
`);
Expand Down Expand Up @@ -806,10 +806,10 @@ describe('components and custom element', () => {
const { code, templates } = compile(input);

expect(code).toMatchInlineSnapshot(`
"import { __rC } from 'azoth/runtime';
"import { __createComponent } from 'azoth/runtime';

const c = __rC(Component, {});
const cProps = __rC(Component, { prop: value, ...spread, attr: "static", });
const c = __createComponent(Component, {});
const cProps = __createComponent(Component, { prop: value, ...spread, attr: "static", });
"
`);
expect(templates).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -844,9 +844,9 @@ describe('components and custom element', () => {
const input = `const c = <Component data-id={x} aria-label={y} name={z} />;`;
const { code } = compile(input);
expect(code).toMatchInlineSnapshot(`
"import { __rC } from 'azoth/runtime';
"import { __createComponent } from 'azoth/runtime';

const c = __rC(Component, { "data-id": x, "aria-label": y, name: z, });
const c = __createComponent(Component, { "data-id": x, "aria-label": y, name: z, });
"
`);
});
Expand Down Expand Up @@ -899,10 +899,10 @@ describe('components and custom element', () => {
const { code, templates } = compile(input);

expect(code).toMatchInlineSnapshot(`
"import { __rC } from 'azoth/runtime';
"import { __createComponent } from 'azoth/runtime';
import { t6e8375c4 } from 'virtual:azoth-templates?id=6e8375c4';
const $A = __rC(A, {});
const $B = __rC(B, {});
const $A = __createComponent(A, {});
const $B = __createComponent(B, {});
const dom = t6e8375c4($A,$B);
"
`);
Expand Down Expand Up @@ -968,14 +968,14 @@ describe('components and custom element', () => {
const { code, templates } = compile(input);

expect(code).toMatchInlineSnapshot(`
"import { __rC } from 'azoth/runtime';
"import { __createComponent } from 'azoth/runtime';
import { t15aa2705, t15aa2705_1, t15aa2705_2, t15aa2705_3, t982d9e3e, t9155678a } from 'virtual:azoth-templates?id=15aa2705&id=15aa2705&id=15aa2705&id=15aa2705&id=982d9e3e&id=9155678a';
const c = __rC(Component, {}, t15aa2705("test"));
const cTrim = __rC(Component, {}, t15aa2705_1("test"));
const cTrimStart = __rC(Component, {}, t15aa2705_2("test"));
const cTrimEnd = __rC(Component, {}, t15aa2705_3("test"));
const cText = __rC(Component, {}, t982d9e3e());
const cFrag = __rC(Component, {}, t9155678a(1,2));
const c = __createComponent(Component, {}, t15aa2705("test"));
const cTrim = __createComponent(Component, {}, t15aa2705_1("test"));
const cTrimStart = __createComponent(Component, {}, t15aa2705_2("test"));
const cTrimEnd = __createComponent(Component, {}, t15aa2705_3("test"));
const cText = __createComponent(Component, {}, t982d9e3e());
const cFrag = __createComponent(Component, {}, t9155678a(1,2));
"
`);

Expand Down Expand Up @@ -1412,12 +1412,12 @@ describe('controller', () => {
const { code, templates } = compile(input);

expect(code).toMatchInlineSnapshot(`
"import { __rC } from 'azoth/runtime';
"import { __createComponent } from 'azoth/runtime';
import { t3514925d, tab8527d7, t286a9d57 } from 'virtual:azoth-templates?id=3514925d&id=ab8527d7&id=286a9d57';
const C = Updater.for(({status}, childNodes) => t3514925d("status",childNodes));
const Greeting = Controller.for(({name}) => tab8527d7(name));
const greeting = Greeting.render(data);
const t = __rC(C, { status: status, }, t286a9d57(greeting));
const t = __createComponent(C, { status: status, }, t286a9d57(greeting));
"
`);
expect(templates).toMatchInlineSnapshot(`
Expand Down
Loading
Loading