neo.mjs 10.0.0-beta.6 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.1.md +20 -0
  2. package/.github/RELEASE_NOTES/v10.0.0-beta.2.md +73 -0
  3. package/.github/RELEASE_NOTES/v10.0.0-beta.3.md +39 -0
  4. package/.github/RELEASE_NOTES/v10.0.0.md +52 -0
  5. package/ServiceWorker.mjs +2 -2
  6. package/apps/portal/index.html +1 -1
  7. package/apps/portal/view/ViewportController.mjs +6 -4
  8. package/apps/portal/view/examples/List.mjs +28 -19
  9. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  10. package/examples/functional/button/base/MainContainer.mjs +207 -0
  11. package/examples/functional/button/base/app.mjs +6 -0
  12. package/examples/functional/button/base/index.html +11 -0
  13. package/examples/functional/button/base/neo-config.json +6 -0
  14. package/learn/blog/v10-deep-dive-functional-components.md +293 -0
  15. package/learn/blog/v10-deep-dive-reactivity.md +522 -0
  16. package/learn/blog/v10-deep-dive-state-provider.md +432 -0
  17. package/learn/blog/v10-deep-dive-vdom-revolution.md +194 -0
  18. package/learn/blog/v10-post1-love-story.md +383 -0
  19. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +26 -2
  20. package/package.json +3 -3
  21. package/src/DefaultConfig.mjs +2 -2
  22. package/src/Neo.mjs +47 -45
  23. package/src/component/Abstract.mjs +412 -0
  24. package/src/component/Base.mjs +18 -380
  25. package/src/core/Base.mjs +34 -33
  26. package/src/core/Effect.mjs +30 -34
  27. package/src/core/EffectManager.mjs +101 -14
  28. package/src/core/Observable.mjs +69 -65
  29. package/src/form/field/Text.mjs +11 -5
  30. package/src/functional/button/Base.mjs +384 -0
  31. package/src/functional/component/Base.mjs +51 -145
  32. package/src/layout/Cube.mjs +8 -4
  33. package/src/manager/VDomUpdate.mjs +179 -94
  34. package/src/mixin/VdomLifecycle.mjs +4 -1
  35. package/src/state/Provider.mjs +41 -27
  36. package/src/util/VDom.mjs +11 -4
  37. package/src/util/vdom/TreeBuilder.mjs +38 -62
  38. package/src/worker/mixin/RemoteMethodAccess.mjs +1 -6
  39. package/test/siesta/siesta.js +15 -3
  40. package/test/siesta/tests/VdomCalendar.mjs +7 -7
  41. package/test/siesta/tests/VdomHelper.mjs +7 -7
  42. package/test/siesta/tests/classic/Button.mjs +113 -0
  43. package/test/siesta/tests/core/EffectBatching.mjs +46 -41
  44. package/test/siesta/tests/functional/Button.mjs +113 -0
  45. package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +59 -0
  46. package/test/siesta/tests/vdom/Advanced.mjs +14 -8
  47. package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +9 -9
  48. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +6 -6
  49. package/test/siesta/tests/vdom/layout/Cube.mjs +11 -7
  50. package/test/siesta/tests/vdom/table/Container.mjs +9 -5
  51. package/src/core/EffectBatchManager.mjs +0 -67
@@ -0,0 +1,522 @@
1
+ # Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity
2
+
3
+ In the main article of our series, we explored the "heartbreak" of modern frontend development: the constant battle
4
+ against the main thread, the tedious "memoization tax," and the architectural nightmares of complex state management.
5
+ These are not isolated issues; they are symptoms of a foundational problem in how mainstream frameworks handle reactivity.
6
+
7
+ Welcome to the first deep dive into the architecture of Neo.mjs v10. In this article, we're going to dissect the engine
8
+ that makes the old problems obsolete: **The Two-Tier Reactivity System**. This is a revolutionary approach that
9
+ seamlessly unifies two powerful paradigms—a classic "push" system and a modern "pull" system—into one elegant
10
+ developer experience. This isn't just a new feature; it's a new reality for how you can write and reason about your
11
+ application's state and rendering logic.
12
+
13
+ *(Part 2 of 5 in the v10 blog series. Details at the bottom.)*
14
+
15
+ ---
16
+
17
+ ## Act I: Tier 1 - The Classic "Push" System
18
+
19
+ Unlike many frameworks, Neo.mjs has *always* had a reactive config system. Since its earliest versions, you could take a
20
+ component instance and change its properties directly, and the UI would update automatically.
21
+
22
+ ```javascript
23
+ // This has always worked in Neo.mjs
24
+ const myButton = Neo.get('my-button');
25
+ myButton.text = 'Click me now!'; // The button's text in the DOM updates
26
+ ```
27
+
28
+ This has always been powered by a robust system of prototype-based getters and setters.
29
+ For any reactive **Named Config** (e.g., `text_`), the framework provides three optional lifecycle hooks that you can
30
+ implement to hook into its lifecycle:
31
+
32
+ * `beforeGetText(value)`: Run just before a value is read.
33
+ * `beforeSetText(value, oldValue)`: Run before a new value is set, allowing for validation or transformation.
34
+ * `afterSetText(value, oldValue)`: Run after a value has been successfully changed, perfect for triggering side effects.
35
+
36
+ This powerful, hook-based API is an imperative, **"push-based"** system. Think of it like a **manual phone tree**: when a
37
+ config changes, your `afterSet` hook is responsible for explicitly "calling" all the other parts of the component
38
+ that need to know about the change. It offers precise, granular control, but it means you are manually managing the
39
+ dependency graph.
40
+
41
+ ```javascript readonly
42
+ // Example: Implementing an afterSet hook
43
+ import Base from 'neo.mjs/src/core/Base.mjs';
44
+
45
+ class MyComponent extends Base {
46
+ static config = {
47
+ className: 'My.AfterSetExample',
48
+ // A reactive config with a trailing underscore
49
+ message_: 'Hello'
50
+ }
51
+
52
+ // This hook automatically runs after 'message' is set
53
+ afterSetMessage(value, oldValue) {
54
+ console.log(`Message changed from "${oldValue}" to "${value}"`);
55
+ // Manually update a dependent property or trigger a UI update
56
+ this.someOtherProperty = `Processed: ${value.toUpperCase()}`;
57
+ }
58
+ }
59
+
60
+ const myInstance = Neo.create(MyComponent);
61
+ myInstance.message = 'World'; // Console will log: Message changed from "Hello" to "World"
62
+ console.log(myInstance.someOtherProperty); // Logs: Processed: WORLD
63
+ ```
64
+
65
+ For v10, we didn't replace this system—we super-charged it. We asked: what if we could add a second, fully automatic
66
+ tier to this foundation?
67
+
68
+ ## Act II: Tier 2 - The Modern "Pull" System
69
+
70
+ The v10 release introduces the second tier: a declarative, **"pull-based"** system. Think of it like a **subscription
71
+ service**: you "subscribe" to a piece of state simply by reading it. When that state changes, the framework automatically
72
+ notifies all subscribers. You no longer manage the dependency graph—the framework does it for you.
73
+
74
+ This is powered by a new set of core primitives (`Neo.core.Config`, `Neo.core.Effect`, `Neo.core.EffectManager`)
75
+ that form a hyper-performant reactive foundation.
76
+
77
+ The true genius of this Two-Tier system is how they are seamlessly bridged together. Think of it like a **universal
78
+ power adapter**: you use a simple, familiar plug (`myButton.text = '...'`), and the adapter transparently handles
79
+ powering both systems at once.
80
+
81
+ When you define a a config with a trailing underscore (e.g., `text_`), the generated setter becomes this adapter. It
82
+ simultaneously:
83
+
84
+ 1. **Powers Tier 2 ("Pull"):** It updates the underlying `Neo.core.Config` atom, automatically triggering any dependent effects.
85
+ 2. **Powers Tier 1 ("Push"):** It calls the classic `afterSetText()` hook, allowing for explicit, imperative logic.
86
+
87
+ This means every config property is now an observable, atomic unit of state that works with both paradigms, giving you
88
+ the best of both worlds without any extra effort.
89
+
90
+ This upgrade set the stage for a revolutionary new way to think about component state.
91
+
92
+ ## Act III: The Breakthrough - A Tale of Two States
93
+
94
+ The true power of the **Two-Tier Reactivity System** is not just that the two tiers exist, but how they work together.
95
+ With this unified engine in place, we could design a functional component model that solves one of the biggest
96
+ architectural challenges in modern UI development: the ambiguity between a component's public API and its private state.
97
+
98
+ This is best explained with a simple component:
99
+
100
+ ```javascript
101
+ import {defineComponent, useConfig, useEvent} from 'neo.mjs';
102
+
103
+ export default defineComponent({
104
+ // 1. The Public API
105
+ config: {
106
+ className: 'My.Component',
107
+ greeting_: 'Hello' // This is a NAMED config
108
+ },
109
+
110
+ // 2. The Implementation
111
+ createVdom(config) {
112
+ // 3. The Private State
113
+ const [name, setName] = useConfig('World'); // This is an ANONYMOUS config
114
+
115
+ useEvent('click', () => setName(prev => prev === 'Neo' ? 'World' : 'Neo'));
116
+
117
+ return {
118
+ // 4. The Synergy
119
+ text: `${config.greeting}, ${name}!`
120
+ }
121
+ }
122
+ });
123
+ ```
124
+
125
+ This small component demonstrates a paradigm that is likely unfamiliar to developers coming from other frameworks.
126
+ Let's break it down.
127
+
128
+ #### 1. Named Configs: The Public, Mutable API
129
+
130
+ The `greeting_` property is a **Named Config**. It is defined inside the `static config` block. (The trailing underscore
131
+ is the Neo.mjs convention to automatically generate a reactive getter and setter for a public property named `greeting`.)
132
+ Think of it as the component's public-facing API.
133
+
134
+ * **It's like props:** A parent component can provide an initial value for `greeting` when creating an instance.
135
+ * **It's NOT like props:** It is fully reactive and **directly mutable** from the outside.
136
+
137
+ Another component, or you directly in the browser console, can do this:
138
+
139
+ ```javascript
140
+ const myComponent = Neo.get('my-component-id');
141
+
142
+ // Directly change the public API. The component will instantly re-render.
143
+ myComponent.greeting = 'Welcome';
144
+ ```
145
+
146
+ This is a paradigm shift. It's not "props drilling" or complex state management. It's a direct, observable,
147
+ and reactive contract with the component.
148
+
149
+ #### 2. Anonymous Configs: The Private, Encapsulated State
150
+
151
+ The `const [name, setName] = useConfig('World')` line creates an **Anonymous Config**.
152
+
153
+ * **It's like `useState`:** It manages a piece of state that is completely private and encapsulated within the component.
154
+ * **It's NOT controllable from the outside:** No parent component or external code can see or modify the `name` state.
155
+ As shown in the example, it can only be changed via the `setName` function, which is called by the component's own
156
+ internal logic (like the `useEvent` hook).
157
+
158
+ #### 3. The Synergy: Effortless Composition
159
+
160
+ The magic happens inside the `createVdom` method. This single function, which is wrapped in a master `Neo.core.Effect`,
161
+ seamlessly reads from both state types:
162
+
163
+ * It accesses the public API via the `config` parameter. This object is a reactive proxy to the component's public API.
164
+ When the `vdomEffect` runs, simply accessing `config.greeting` is enough to register the public `greeting_` property
165
+ as a dependency.
166
+ * It accesses the private state directly from the hook's return value: `name`.
167
+
168
+ Because both `config.greeting` (a Named Config) and `name` (an Anonymous Config) are powered by the same atomic
169
+ `Neo.core.Config` engine, the master `vdomEffect` automatically tracks them both as dependencies.
170
+
171
+ If *either* an external force changes the public API (`myComponent.greeting = '...'`) or an internal event changes the
172
+ private state (`setName('Neo')`), the component's `vdomEffect` will re-run, and the UI will be updated surgically.
173
+
174
+ ### Conclusion: The Best of Both Worlds
175
+
176
+ This "Tale of Two States" is more than just a new API; it's the foundation for a paradigm that solves the most
177
+ frustrating parts of modern frontend development. It delivers a developer experience that feels both radically simple
178
+ and incredibly powerful, resolving the long-standing conflict between mutability and predictability.
179
+
180
+ **1. Your State is Mutable by Design.**
181
+ In Neo.mjs, you are encouraged to work with state in the most natural way possible: direct mutation. The framework
182
+ provides several powerful methods to apply these mutations, from changing single properties to batching multiple updates
183
+ atomically, or even decoupling state changes from the render cycle entirely.
184
+
185
+ ```javascript
186
+ // The recommended way is to mutate a component's public configs.
187
+ // The component's internal logic (e.g., an afterSet hook) directly mutates the vdom object, outside any effects.
188
+ // This triggers an asynchronous update cycle.
189
+ myComponent.text = 'New Title';
190
+
191
+ // For multiple changes, batch them with .set() for efficiency.
192
+ await myComponent.set({
193
+ iconCls: 'fa fa-rocket',
194
+ text : 'Launch'
195
+ });
196
+
197
+ // Change multiple configs without triggering an update cycle:
198
+ myComponent.setSilent({
199
+ iconCls: 'fa fa-cogs',
200
+ text : 'Settings'
201
+ });
202
+ // This is a powerful way to e.g. then update its parent, and trigger an aggregated update cycle for both
203
+ ```
204
+
205
+ **2. The Update Process is Immutable by Default.**
206
+ Herein lies the magic. The moment you trigger an update, the framework takes a complete, serializable snapshot of your
207
+ component's current `vdom` and `vnode`. This JSON snapshot is, by its nature, an immutable copy. It's this frozen-in-time
208
+ representation that gets sent to the VDOM Worker for diffing.
209
+
210
+ **The Result: A Mutability Paradox.**
211
+ You get the best of both worlds, without compromise:
212
+
213
+ * **A Simple, Mutable Developer Experience:** You work with plain JavaScript objects and change them directly.
214
+ The framework doesn't force you into an unnatural, immutable style.
215
+ * **A Safe, Immutable Update Pipeline:** The VDOM worker operates on a predictable, isolated snapshot,
216
+ ensuring that rendering is always consistent and free from race conditions.
217
+
218
+ Because of this architecture, you are free to continue mutating the component's state in the App Worker *even while a
219
+ VDOM update is in flight*. The framework handles the queueing and ensures the next update will simply capture the new state.
220
+
221
+ This is why the entire ecosystem of manual memoization (`useMemo`, `useCallback`, `React.memo`) is rendered obsolete.
222
+ The architecture is **performant by default** because it gives you the developer ergonomics of direct mutation while
223
+ leveraging the performance and safety of an immutable, off-thread rendering process.
224
+
225
+ This is the new reality of reactivity in Neo.mjs v10. It's a system designed to let you fall in love with building,
226
+ not fighting, your components.
227
+
228
+ ---
229
+
230
+ ## Under the Hood: The Atomic Engine
231
+
232
+ For those who want to go deeper, let's look at the core primitives that make this all possible. The entire v10 reactivity
233
+ system is built on a foundation of three simple, powerful classes.
234
+
235
+ ### `Neo.core.Config`: The Observable Box
236
+ [[Source]](https://github.com/neomjs/neo/blob/dev/src/core/Config.mjs)
237
+
238
+ At the very bottom of the stack is `Neo.core.Config`. You can think of this as an "observable box." It's a lightweight
239
+ container that holds a single value. Its only jobs are to hold that value and to notify a list of subscribers whenever
240
+ the value changes. It knows nothing about components, the DOM, or anything else.
241
+
242
+ ```javascript readonly
243
+ // Example: Neo.core.Config - The Observable Box
244
+ import Config from 'neo.mjs/src/core/Config.mjs';
245
+
246
+ const myConfig = new Config('initial value');
247
+ ```
248
+
249
+ ### `Neo.core.Effect`: The Reactive Function
250
+ [[Source]](https://github.com/neomjs/neo/blob/dev/src/core/Effect.mjs)
251
+
252
+ An `Effect` is a function that automatically tracks its dependencies. When you create an `Effect`, you give it a function
253
+ to run. As that function runs, any `Neo.core.Config` instance whose value it reads will automatically register itself as
254
+ a dependency of that `Effect`.
255
+
256
+ If any of those dependencies change in the future, the `Effect` automatically re-runs its function. It's a self-managing
257
+ subscription that forms the basis of all reactivity in the framework.
258
+
259
+ ```javascript readonly
260
+ // Example: Neo.core.Effect - The Reactive Function
261
+ import Effect from 'neo.mjs/src/core/Effect.mjs';
262
+ import Config from 'neo.mjs/src/core/Config.mjs';
263
+
264
+ let effectRunCount = 0;
265
+ const myConfig = new Config('initial value'); // Re-using myConfig from previous example
266
+
267
+ const myEffect = new Effect(() => {
268
+ effectRunCount++;
269
+ console.log('Effect ran. Current config value:', myConfig.get());
270
+ });
271
+
272
+ console.log('Initial effect run count:', effectRunCount); // Logs: Initial effect run count: 1
273
+
274
+ myConfig.set('new value'); // Console will log: Effect ran. Current config value: new value
275
+ console.log('After set, effect run count:', effectRunCount); // Logs: After set, effect run count: 2
276
+ ```
277
+
278
+ ### `Neo.core.EffectManager`: The Orchestrator
279
+ [[Source]](https://github.com/neomjs/neo/blob/dev/src/core/EffectManager.mjs)
280
+
281
+ This is the central singleton that makes the magic happen. The `EffectManager` keeps track of which `Effect` is currently
282
+ running. When a `Config` instance is read, it asks the `EffectManager`, "Who is watching me right now?" and adds the
283
+ current `Effect` to its list of subscribers.
284
+
285
+ ### The Next Level: Mutable State, Immutable Updates
286
+
287
+ This is where the Neo.mjs reactivity model takes a significant leap beyond other frameworks. It's an architecture that
288
+ provides the intuitive ergonomics of direct mutation with the safety and performance of an immutable pipeline.
289
+
290
+ #### Synchronous State, Asynchronous DOM
291
+
292
+ First, a crucial distinction. The core `Effect` system within the App Worker runs **synchronously**, and it's built on a
293
+ principle of **atomic batching**. When you use a method like `myComponent.set({...})`, the framework automatically wraps
294
+ all state changes in a single batch. The `EffectManager` pauses execution, queues all triggered effects, and then runs
295
+ them exactly once, synchronously, after the batch is complete. This guarantees that all dependent reactive values
296
+ *within the App Worker* are updated immediately and consistently in the same turn of the event loop, with no "waiting for
297
+ the next tick" to know the state of your application logic.
298
+
299
+ However, the process of updating the actual DOM is **asynchronous**. It has to be. A call to `myComponent.update()` or a
300
+ change to a reactive config kicks off the "triangular worker communication":
301
+
302
+ 1. **App Worker → VDOM Worker:** The App Worker sends a snapshot of the component's `vdom` and previous `vnode` to the VDOM Worker.
303
+ 2. **VDOM Worker → Main Thread:** The VDOM Worker creates the new `vnode` tree. calculates the minimal set of changes
304
+ (the `deltas`). It sends both to the Main Thread.
305
+ 3. **Main Thread → App Worker:** The Main Thread applies the `deltas` to the real DOM. It then sends the new `vnode`
306
+ back to the App Worker, which assigns it to the component (`myComponent.vnode = newVnode`) and resolves any promises
307
+ associated with the update cycle.
308
+
309
+ #### The Immutable Snapshot: The Key to the Paradox
310
+
311
+ The genius of this model lies in how the App Worker communicates with the VDOM Worker. It doesn't send a live, mutable
312
+ object. Instead, it creates a deep, JSON-serializable **snapshot** of the component's `vdom` tree.
313
+
314
+ This snapshot is, by its very nature, an **immutable copy**.
315
+
316
+ This single architectural choice unlocks the entire paradigm:
317
+
318
+ * **Developer Freedom:** As a developer in the App Worker, you are free to mutate your component's state and VDOM at
319
+ any time. You can change a property, push a new child into the `vdom.cn` array, and then immediately change another property.
320
+ * **Pipeline Safety:** The VDOM worker receives a clean, predictable, "frozen-in-time" version of the UI to work with.
321
+ It is completely isolated from any mutations that might be happening back in the App Worker while it's calculating the diff.
322
+
323
+ This completely eliminates the need for developers to manage immutability. You get a developer experience that is
324
+ fundamentally simpler and more aligned with how JavaScript objects naturally work, while the framework ensures the update
325
+ process is as safe and predictable as in the most rigidly immutable systems.
326
+
327
+ ### Tying It All Together
328
+
329
+ When you define a component, the framework connects these pieces for you:
330
+
331
+ 1. Every reactive config (both **Named** like `greeting_` and **Anonymous** via `useConfig`) is backed by its own
332
+ `Neo.core.Config` instance.
333
+ 2. Your entire `createVdom` function is wrapped in a single, master `Neo.core.Effect`.
334
+ 3. When `createVdom` runs, it reads from various `Config` instances, and the `EffectManager` ensures they are all
335
+ registered as dependencies of the master `Effect`.
336
+ 4. When any of those configs change, the master `Effect` re-runs, your `createVdom` is executed again, and the UI updates.
337
+
338
+ This elegant, layered architecture is what provides the power and performance of the v10 reactivity system, delivering a
339
+ developer experience that is both simple on the surface and incredibly robust underneath.
340
+
341
+ ---
342
+
343
+ ## Architectural Proof: The Asynchronous Lifecycle
344
+
345
+ The Two-Tier Reactivity system isn't just for managing the state inside a single component. Its true power is revealed
346
+ when it's used to solve complex, application-wide architectural challenges. The most potent example of this is how
347
+ Neo.mjs v10 handles the "lazy-load paradox."
348
+
349
+ This is enabled by three fundamental v10 features: enhanced mixins, an async-aware lifecycle, and intelligent remote
350
+ method interception.
351
+
352
+ ### 1. Enhanced Mixins: True Modules of State and Behavior
353
+
354
+ This is a core tenet of the Neo.mjs philosophy: **architectural depth enables surface-level simplicity.**
355
+
356
+ For v10, we revolutionized how our class system handles **mixins**. Previously, they could only copy methods. Now, they
357
+ can also carry their own `configs`, elevating them into truly self-contained modules of both state and behavior. This
358
+ allows us to encapsulate complex logic (e.g., for rendering or remote communication) into single, reusable modules that
359
+ can be cleanly applied to any class.
360
+
361
+ ### 2. A Two-Phase, Async-Aware Lifecycle (`initAsync`)
362
+
363
+ Every class in Neo.mjs now has a two-phase initialization process. The `construct()` method runs instantly and
364
+ synchronously. It is then followed by `initAsync()`, an `async` method designed for long-running tasks.
365
+ The framework provides a reactive `isReady_` config that automatically flips to `true` only after the `initAsync()`
366
+ promise resolves.
367
+
368
+ ```javascript readonly
369
+ // Example: Two-Phase, Async-Aware Lifecycle (initAsync)
370
+ import Base from 'neo.mjs/src/core/Base.mjs';
371
+
372
+ class MyAsyncService extends Base {
373
+ static config = {
374
+ className: 'My.AsyncService',
375
+ // isReady_ is automatically managed by the framework
376
+ }
377
+
378
+ async initAsync() {
379
+ await super.initAsync(); // Mandatory: Await the parent's initAsync
380
+ console.log('initAsync started. Simulating async work...');
381
+ await this.timeout(1000); // Simulate async work
382
+ console.log('initAsync finished.');
383
+ // isReady will flip to true *after* this promise resolves,
384
+ // triggering afterSetIsReady()
385
+ }
386
+
387
+ // This hook is called by the framework when isReady_ changes
388
+ afterSetIsReady(value, oldValue) {
389
+ super.afterSetIsReady(value, oldValue); // Call super if it exists
390
+ if (value === true) {
391
+ console.log('MyAsyncService is now ready!');
392
+ }
393
+ }
394
+ }
395
+
396
+ const service = Neo.create(MyAsyncService);
397
+ console.log('Service created. isReady (initial):', service.isReady); // Logs: Service created. isReady (initial): false
398
+ // Console will then log:
399
+ // initAsync started. Simulating async work...
400
+ // initAsync finished.
401
+ // MyAsyncService is now ready!
402
+ ```
403
+
404
+ ### 3. Intelligent Remote Method Interception
405
+
406
+ The framework's `RemoteMethodAccess` mixin is aware of this `isReady` state. When a remote call arrives for a main
407
+ thread addon that is not yet ready, it doesn't fail. Instead, it **intercepts the call**.
408
+
409
+ Let's walk through a practical example: using a powerful, but large, third-party charting library like AmCharts on the
410
+ main thread.
411
+
412
+ * Loading it upfront is bad for performance; it blocks the initial application load.
413
+ * Lazy-loading it creates a classic race condition: what happens if your App Worker sends a command to create a chart
414
+ *before* the AmCharts library has finished downloading and initializing?
415
+
416
+ In a traditional framework, this would require complex, manual state management. In Neo.mjs, the solution is an
417
+ elegant and automatic feature of the core reactivity system.
418
+
419
+ 1. An `AmChart` wrapper component in the App Worker is mounted and sends a remote command: `Neo.main.addon.AmCharts.create(...)`.
420
+ 2. On the main thread, the `AmCharts` addon receives the call. It checks its own `isReady` state, which is `false`.
421
+ 3. Instead of executing the `create` method, it **caches the request** in an internal queue.
422
+ 4. Crucially, it **immediately triggers its own `initAsync()` process**, which begins downloading the AmCharts library files.
423
+ 5. Once the files are loaded, `initAsync()` resolves, and the addon's `isReady` flag flips to `true`.
424
+ 6. The `afterSetIsReady()` hook—a standard feature of the reactivity system—automatically fires, processes the queue of
425
+ cached calls, and finally creates the chart.
426
+
427
+ The developer in the App Worker is completely shielded from this complexity. They simply call a method, and the framework
428
+ guarantees it will be executed correctly and in the right order. There are no manual loading flags, no race conditions,
429
+ and no complex queueing logic to write.
430
+
431
+ ```javascript readonly
432
+ // Example: Intelligent Remote Method Interception (Simplified)
433
+
434
+ // --- Main Thread Addon ---
435
+ // This addon runs on the Main Thread and simulates loading a heavy library.
436
+ import AddonBase from 'neo.mjs/src/main/addon/Base.mjs';
437
+
438
+ class MyHeavyLibraryAddon extends AddonBase {
439
+ static config = {
440
+ className: 'Neo.main.addon.HeavyLibraryAddon',
441
+ // List methods that should be intercepted if the addon is not ready.
442
+ // The base class's onInterceptRemotes() will cache these calls.
443
+ interceptRemotes: ['loadResource', 'processData'],
444
+ // Expose the methods to the App Worker.
445
+ remotes: {
446
+ app: ['loadResource', 'processData']
447
+ }
448
+ }
449
+
450
+ // Subclasses must implement loadFiles() to load external resources.
451
+ // This method is awaited by initAsync().
452
+ async loadFiles() {
453
+ console.log('Addon: Simulating heavy library/resource loading...');
454
+ await this.timeout(1500); // Simulate async work
455
+ console.log('Addon: Heavy library/resource loaded.');
456
+ }
457
+
458
+ // Remote methods that can be called from the App Worker.
459
+ loadResource(url) {
460
+ console.log('Addon: Executing loadResource for:', url);
461
+ return `Resource from ${url} loaded!`;
462
+ }
463
+
464
+ processData(data) {
465
+ console.log('Addon: Executing processData with:', data);
466
+ return `Data processed: ${JSON.stringify(data)}`;
467
+ }
468
+
469
+ // The afterSetIsReady method (from AddonBase) will automatically
470
+ // process any queued remote calls once this.isReady becomes true.
471
+ }
472
+
473
+ // --- Simulation of App Worker making calls to Main Thread Addon ---
474
+ (async () => {
475
+ console.log('--- Simulation Start ---');
476
+
477
+ // These calls are made before the addon's initAsync (and thus loadFiles) completes.
478
+ // They will be intercepted and queued by the addon.Base logic.
479
+ console.log('Simulating App Worker call: loadResource (before addon ready)');
480
+ const result1Promise = Neo.main.addon.HeavyLibraryAddon.loadResource('/api/data/resource1');
481
+
482
+ console.log('Simulating App Worker call: processData (before addon ready)');
483
+ const result2Promise = Neo.main.addon.HeavyLibraryAddon.processData({ value: 42, type: 'example' });
484
+
485
+ // The promises will resolve once the addon becomes ready and processes the queued calls.
486
+ const [result1, result2] = await Promise.all([result1Promise, result2Promise]);
487
+
488
+ console.log('Result from loadResource:', result1);
489
+ console.log('Result from processData:', result2);
490
+
491
+ console.log('--- Simulation End ---');
492
+ })();
493
+ ```
494
+
495
+ **Explanation of this example's relevance:**
496
+ This snippet demonstrates how Neo.mjs handles remote method calls to Main Thread addons that might not be immediately ready.
497
+
498
+ * `MyHeavyLibraryAddon` (Main Thread Addon):
499
+ * Extends AddonBase, inheriting the core logic for initAsync, isReady_, onInterceptRemotes, and afterSetIsReady.
500
+ * Defines interceptRemotes to specify which methods should be queued if the addon isn't ready.
501
+ * Implements loadFiles() to simulate the asynchronous loading of external resources (e.g., a large third-party library).
502
+ * Exposes loadResource and processData as remote methods that can be called from the App Worker.
503
+ * Simulation of App Worker Calls:
504
+ * Shows how an App Worker component would make calls to the Main Thread addon using Neo.main.addon.AddonClassName.methodName().
505
+ * These calls are made before the MyHeavyLibraryAddon has completed its initAsync (and loadFiles).
506
+ * The AddonBase's onInterceptRemotes automatically intercepts these calls, queues them, and returns a promise that will resolve later.
507
+ * Once MyHeavyLibraryAddon finishes its initAsync (simulated by loadFiles completing), its isReady_ config flips to true.
508
+ * The AddonBase's afterSetIsReady then automatically processes the queued calls, resolving the original promises.
509
+
510
+ This is the ultimate expression of the Neo.mjs philosophy: using the core reactivity engine not just to render UIs, but
511
+ to orchestrate the entire application's asynchronous state and logic. It's the final proof that a robust reactive foundation
512
+ doesn't just simplify your code — it makes entirely new patterns of development possible.
513
+
514
+ ---
515
+
516
+ ## The Neo.mjs v10 Blog Post Series
517
+
518
+ 1. [A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow](./v10-post1-love-story.md)
519
+ 2. Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity
520
+ 3. [Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
521
+ 4. [Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering](./v10-deep-dive-vdom-revolution.md)
522
+ 5. [Deep Dive: The State Provider Revolution](./v10-deep-dive-state-provider.md)