Skip to content

Commit 7e25c97

Browse files
committed
add mental model
1 parent 1c1baac commit 7e25c97

File tree

1 file changed

+345
-0
lines changed

1 file changed

+345
-0
lines changed

docs/MENTAL-MODEL.md

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
# My Mental Model of Azoth
2+
3+
*A document where I (the LLM) make explicit my understanding of Azoth, for review and correction.*
4+
5+
---
6+
7+
## What I Believe I Understand
8+
9+
### JSX → DOM Literals
10+
11+
When I write `<p>hello</p>` in Azoth, the expression evaluates to an actual `HTMLParagraphElement`. This is not a description or instruction — it's the real DOM node, ready to use.
12+
13+
**Contrast with React:** React JSX returns a plain object (`{ type: 'p', props: {...} }`) that describes what to render. A reconciler later creates/updates actual DOM.
14+
15+
**Implication:** I can immediately use DOM APIs on JSX output:
16+
```jsx
17+
const list = <ul><li>one</li><li>two</li></ul>;
18+
const items = [...list.children]; // Works immediately
19+
list.querySelector('li').classList.add('first'); // Standard DOM
20+
```
21+
22+
### No Framework Layer To Go Through
23+
24+
This is critical: React returns plain objects because **rendering must happen within React's infrastructure**. React needs to control the process so it knows what's going on. All changes and activity must go through the React framework.
25+
26+
In Azoth, **the compiled/transpiled code IS the runtime**. There's no framework layer mediating between your code and the DOM. The JSX compiles to code that directly creates and returns actual DOM objects. You authored JSX, you get DOM.
27+
28+
**Contrast:** React owns the render cycle. Azoth gets out of your way.
29+
30+
### Just JavaScript, Just DOM
31+
32+
The "binding" in Azoth is really just **async subscription** — it's just JavaScript.
33+
34+
This seems counterintuitive after years of state-driven frameworks, but Azoth lets you think in:
35+
- **JavaScript** — normal values, functions, async patterns
36+
- **DOM** — the actual web platform API
37+
- **Web platform primitives** — no proprietary abstractions
38+
39+
**The API around JSX is the DOM:**
40+
- Outputs are DOM nodes
41+
- Inputs are DOM nodes, JS values, or JS async data structures
42+
- Everything can be reasoned about as normal JavaScript
43+
44+
**No boundary** between Azoth and any web platform technology. Use any browser API, any DOM method, any JavaScript pattern — they all just work.
45+
46+
**Contrast with other frameworks:** Even SolidJS has quirky non-standard JavaScript behavior arising from transpilation rules. Azoth avoids this by keeping the abstraction layer minimal and aligned with the platform.
47+
48+
### No Virtual DOM, No Reconciliation
49+
50+
Azoth doesn't maintain a shadow tree to diff against. The DOM I create IS the state. This is consistent with the original vision for HTML in the browser — the DOM is the source of truth.
51+
52+
**Contrast with React:** React diffs virtual trees and batches updates. You work with state, React syncs to DOM.
53+
54+
### Updates Come From Async Data Structures
55+
56+
Azoth accepts asynchronous data structures in JSX child expressions:
57+
- Promises
58+
- Async generators
59+
- Web streams
60+
- Observables
61+
62+
When these async sources fire (promise resolves, generator yields, stream emits, observable fires), a new HTML payload gets delivered through the transpiled runtime code.
63+
64+
**Concrete example from tests:**
65+
```jsx
66+
async function* Items() {
67+
yield <p>Loading...</p>; // DOM shows: <p>Loading...</p>
68+
yield <ul><li>one</li></ul>; // DOM shows: <ul>... (replaces previous)
69+
yield <ul><li>one</li><li>two</li></ul>; // DOM shows: updated list
70+
}
71+
72+
// In JSX: <div>{Items()}</div>
73+
```
74+
75+
Each yield delivers **JSX** (DOM), not just values. The DOM starts with a comment anchor (`<!--0-->`), then each yield **replaces** the previous content. It's not value interpolation — it's DOM replacement.
76+
77+
**The model:** Changes to the UI happen as change instructions over time. This is hypermedia thinking — similar to what HTMX talks about, but more comprehensive and client-side rather than server-oriented.
78+
79+
### The Fundamental Model: Events = Deltas
80+
81+
This is the core of Azoth's architecture. The **event-driven** view and the **hypermedia** view are the same thing:
82+
83+
```
84+
ui₀ = initial render (page load event)
85+
ui₁ = ui₀ + Δ (promise resolved)
86+
ui₂ = ui₁ + Δ (generator yielded)
87+
ui₃ = ui₂ + Δ (stream emitted)
88+
ui₄ = ui₃ + Δ (user clicked button)
89+
...
90+
uiₙ = uiₙ₋₁ + Δ (any event)
91+
```
92+
93+
**Event sources:**
94+
1. **Page load** — initial render
95+
2. **Async data structures** — promises, generators, streams, observables firing
96+
3. **DOM user events** — clicks, inputs, etc. wired up during render
97+
98+
**The events ARE the deltas.** Each event delivers a change instruction (Δ) to the current UI state.
99+
100+
DOM user events are a major driver of this event-driven architecture. During initial (or subsequent) renders, event handlers get wired to DOM elements. These handlers can:
101+
- Dispatch new async actions (which deliver future deltas)
102+
- Make synchronous changes directly (e.g., via observables)
103+
104+
The DOM events themselves come from outside the Azoth boundary — they're just standard DOM event handling. But they integrate naturally because Azoth IS the DOM.
105+
106+
- No full re-render
107+
- No virtual DOM diffing
108+
- Just incremental change instructions applied directly to the DOM
109+
110+
The DOM is the app state. The UI evolves through a sequence of deltas, each triggered by an event.
111+
112+
### Alignment with the Original Web Platform
113+
114+
This event-driven, delta-based model isn't novel — **it's how the browser was designed to work from the beginning**.
115+
116+
The web platform has always been event-driven:
117+
- DOM events fire directly on elements
118+
- Event handlers trigger changes
119+
- The DOM is the source of truth
120+
121+
React and similar frameworks introduced an **abstraction layer** on top of this:
122+
- Virtual DOM as an intermediary
123+
- Synthetic events wrapping native events
124+
- Component state separate from DOM state
125+
- Reconciliation to sync the two
126+
127+
**Azoth rejects this abstraction as an impedance mismatch.** The virtual DOM model adds:
128+
- Complexity (diffing, reconciliation, batching)
129+
- Overhead (memory, CPU for virtual tree operations)
130+
- Cognitive load (reasoning about two states: component state AND DOM state)
131+
- Boundary friction (escaping the framework to use platform APIs)
132+
133+
By working directly with the DOM, Azoth aligns with:
134+
- The browser's native event loop
135+
- Direct DOM manipulation APIs
136+
- The original vision of HTML as a living document
137+
138+
**This is a return to fundamentals**, not a step backward. Modern browsers are highly optimized for direct DOM operations. The abstractions that made sense in 2013 (cross-browser inconsistencies, performance workarounds) are less necessary today.
139+
140+
### Azoth as "Missing Browser Pieces"
141+
142+
A design philosophy: Azoth fills gaps in the web platform, not replaces it.
143+
144+
**What the browser is missing:**
145+
1. **DOM literals in JavaScript** — no native syntax for `<p>hello</p>` yielding a DOM node
146+
2. **Async → DOM integration** — no simple way to pipe async data structures into DOM rendering
147+
148+
Azoth provides exactly these two things. Nothing more.
149+
150+
*Tongue-in-cheek:* Azoth is a suggestion for rounding out the web platform. It takes the **one good idea from React** (JSX as a declarative DOM syntax) and integrates it with the platform rather than building a parallel universe on top of it.
151+
152+
JSX without the baggage. Async without the complexity. DOM without the abstraction.
153+
154+
### HTML Attributes vs DOM Properties
155+
156+
The distinction is about **static vs dynamic**:
157+
158+
- **Static attribute value** (no interpolation) → compiled into the HTML template
159+
- **Dynamic value** (JSX interpolation `{...}`) → DOM property assignment at runtime
160+
161+
```jsx
162+
// Static: becomes HTML attribute in template
163+
<input name="title" required />
164+
165+
// Dynamic: becomes property assignment at runtime
166+
<input value={title} />
167+
```
168+
169+
**Current state:** Attributes need attribute names, properties need DOM property names. Future work: translation layer so developers can use either interchangeably.
170+
171+
**Interesting pattern:** You can use BOTH on the same element — static attribute for initial HTML (no flash of unstyled content), plus dynamic property for runtime updates.
172+
173+
**Contrast with React:** React requires `className`/`htmlFor` not because of "programmatic setting" but because React's JSX transpiles to JavaScript where `class` and `for` are reserved keywords. Azoth doesn't need this guard because static attributes go directly to HTML templates, never touching JS runtime as property names.
174+
175+
### Thoth and Maya
176+
177+
- **Thoth** = Compiler. Transforms JSX into templates + binding code.
178+
- **Maya** = Runtime. Provides composition and rendering services.
179+
180+
### Core Insight: DOM Changes Are Limited
181+
182+
From years of DOM research (including co-maintaining RactiveJS with Rich Harris before Svelte), a key observation: **there are really only two types of DOM changes:**
183+
184+
1. Change attributes/properties on elements
185+
2. Manage 0-to-n lists of nodes
186+
187+
Everything else is derived from these primitives.
188+
189+
### Maya's Architecture Layers
190+
191+
Maya provides **opt-in levels of sophistication**. React-like concepts exist but you opt INTO them.
192+
193+
These are **compositional building blocks** for state management:
194+
195+
| Layer | Purpose | Complexity |
196+
| ------------ | -------------------------------------------------------------- | ---------- |
197+
| **compose** | Value integration, initial render, streaming | Simplest |
198+
| **replace** | Swap content at anchor | Simple |
199+
| **blocks** | Replicated templates, list operations (add/remove/update rows) | Medium |
200+
| **renderer** | Cache DOM, replay bindings, "UI = f(state)" for a section | Advanced |
201+
202+
You combine these as needed for your state management approach.
203+
204+
### Maya's Compose Engine
205+
206+
`compose.js` is the heart of Maya's runtime. It's a **set of rules for any type of value** that resolves inputs to DOM output.
207+
208+
**The resolution chain:** If compose needs a DOM result, it keeps deriving:
209+
- Function? Call it, compose the result
210+
- Class? Instantiate it, compose the result
211+
- Promise? Wait for it, compose the resolved value
212+
- Async iterator? Subscribe, compose each yielded value
213+
- Array? Compose each element
214+
- Node? Insert it
215+
- String/number? Create text node
216+
217+
**`compose` vs `create`:**
218+
- `create` = for Component syntax in JSX (class/function components)
219+
- If top-level JSX is a Component, it creates once and returns the result
220+
- If a Component is inside a larger JSX snippet with intrinsic elements, it must fully resolve to DOM via compose
221+
222+
**Replace vs Accumulate:**
223+
- Default behavior: new values **replace** previous content
224+
- Exception: `ReadableStream` **adds** items (accumulates)
225+
226+
**The anchor mechanism:**
227+
- Comment nodes (`<!--0-->`) serve as positional anchors
228+
- `anchor.data` tracks how many nodes were inserted
229+
- `clear(anchor)` removes previous nodes before replacement
230+
- `replace(anchor, input)` inserts before anchor and increments count
231+
232+
### Blocks (List Management)
233+
234+
The `blocks/` folder contains anchored blocks, keyed blocks, etc. — strategies for managing lists of nodes with specific operations:
235+
- Add rows
236+
- Remove rows
237+
- Update rows
238+
- Different keying strategies
239+
240+
This is where you opt into more sophisticated list handling when needed.
241+
242+
### SyncAsync: Initial Value + Async Data Structure
243+
244+
A critical pattern: provide a **synchronous value** for immediate composition, plus an **async data structure** for future values.
245+
246+
```jsx
247+
SyncAsync.from(
248+
<p>Loading...</p>, // Value: composed immediately (sync)
249+
fetchData().then(data => <Results data={data} />) // Async data structure: delivers future replacements
250+
)
251+
```
252+
253+
**The distinction:**
254+
- First argument: a **value** to compose directly (sync)
255+
- Second argument: an **async data structure** (Promise, generator, stream, observable) for future values
256+
257+
**Why this matters:**
258+
- Many frameworks have async/sync tension (React is mostly async with sync opt-ins)
259+
- High UI/UX work often needs synchronous behavior
260+
- Azoth's library mentality: render sync NOW, follow up with async later
261+
- This is the hypermedia "swap" pattern
262+
263+
**SDK naming:** Current naming (`SyncAsync`) needs improvement — a future refactoring target.
264+
265+
**Future potential:** Compose could accept custom signals for behaviors beyond just replace/add — developer-controlled rendering behavior.
266+
267+
### Renderer (Cached DOM + Replay)
268+
269+
The renderer can **cache DOM nodes and replay bindings**. This is opting into "UI = f(state)" but for a **section** of DOM, not the whole app.
270+
271+
**How it works:** Literally reuses the **same binding function** that applied initial values. No reconciliation process — just re-running the bindings on the cached DOM structure.
272+
273+
**Limitations (by design):**
274+
- Meant for sections of DOM with data updates
275+
- Not for heavy recomposition of DOM blocks
276+
- Must reliably rebind to the initial DOM structure
277+
278+
**Inverse of React's limitation:**
279+
- React: must keep `useState` calls in same order (invocation counting for consistency)
280+
- Azoth: must reliably rebind to initial DOM structure created on first render
281+
282+
Different tradeoffs for different models.
283+
284+
**Key distinction from traditional frameworks:**
285+
- Traditional frameworks = "spreadsheet editors" — any CRUD anywhere, framework manages all state ubiquitously
286+
- Azoth = DOM is state, you **choose** the appropriate strategy for each piece of functionality
287+
288+
You pick the state management approach that fits your use case, rather than having one imposed globally.
289+
290+
### Intentional Data Flow Design
291+
292+
A consequence of Azoth's architecture: **you must understand your application's data flows**.
293+
294+
In generic state management frameworks, you do state management the same way everywhere. In Azoth, you need to think about:
295+
- How will this part of the application be used?
296+
- How does data flow through it?
297+
- What types of changes are expected?
298+
- How should changes be applied?
299+
300+
**This is intentional.** Solutions emerge that are:
301+
- Well thought out
302+
- Appropriate for how they're being used
303+
- Specific to the problem at hand
304+
305+
This requires more upfront thinking but produces more efficient, purpose-built solutions rather than one-size-fits-all approaches.
306+
307+
---
308+
309+
## Questions I Have
310+
311+
1. ~~**Updates:** When data changes, how does the DOM update?~~ **ANSWERED:** Updates come from async data structures firing events (promise resolution, generator yield, stream/observable emission). The transpiled code handles delivering the new HTML payload.
312+
313+
2. ~~**Bindings:**~~ **ANSWERED from tests:** If `name` is a primitive string, it's inserted once. If it's a Promise, the content appears when it resolves. If it's an async generator, each yield replaces the previous content. The "binding" is really just the async subscription.
314+
315+
3. **Components:** I know components exist but we haven't touched them. Are they functions? Classes? Something else?
316+
317+
4. ~~**Fragments:**~~ **ANSWERED:** Nothing special. `<>...</>` returns a standard DocumentFragment. Thoth may collapse unnecessary fragments during compilation, but otherwise they work as expected per the DOM spec.
318+
319+
5. ~~**The runtime exports:**~~ **ANSWERED:** `compose` = resolve any value to DOM and insert at anchor. `createComponent` = instantiate a component and return result. `composeComponent` = instantiate and compose into an anchor. `renderer` = cache DOM + replay bindings for "UI = f(state)" sections.
320+
321+
---
322+
323+
## Where My React Bias Might Be Showing
324+
325+
1. ~~**Thinking in re-renders:**~~ **REFRAMED:** The right question isn't "when does it re-render?" — Azoth is **event-driven**. Page load is the initial event. All subsequent DOM changes are caused by async activity set in motion. There's no render cycle, just events triggering DOM updates.
326+
327+
2. **Component lifecycle:** I assume there's mount/unmount behavior, but maybe DOM elements just... exist and get removed?
328+
329+
3. **State management:** I assume something manages state, but you said there's "no direct tie to state management" — just primitives and async constructs.
330+
331+
4. ~~**Expecting a framework boundary:**~~ **CONFIRMED:** Azoth has no such boundary. There's no "inside Azoth" vs "escaping Azoth" — it's all just JavaScript and DOM. Use any web platform API directly.
332+
333+
---
334+
335+
## Inconsistencies I Notice In My Understanding
336+
337+
1. ~~I said "DOM is the state" but also there's a "binding mechanism"~~ **RESOLVED:** The compiled code sets up listeners for async sources. When they fire, the DOM gets updated. DOM is still the state — the bindings are just the plumbing that delivers updates.
338+
339+
2. Template extraction is "invisible to developers" but I keep mentioning it — should I stop thinking about it? *Probably yes for developer docs, but it's relevant for understanding compilation.*
340+
341+
3. ~~I'm unclear if Azoth is "no framework" or "minimal framework"~~ **CLARIFIED:** The transpiled code IS the runtime. Maya provides services that the transpiled code uses, but there's no framework layer you "go through." It's more like a library the compiled output calls.
342+
343+
---
344+
345+
*Last updated: Based on smoke.test.tsx work and pivot-feasibility planning*

0 commit comments

Comments
 (0)