neo.mjs 10.1.1 → 10.2.1

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 (49) hide show
  1. package/.github/RELEASE_NOTES/v10.2.0.md +34 -0
  2. package/.github/RELEASE_NOTES/v10.2.1.md +17 -0
  3. package/ServiceWorker.mjs +2 -2
  4. package/apps/covid/view/GalleryContainer.mjs +1 -1
  5. package/apps/covid/view/HelixContainer.mjs +1 -1
  6. package/apps/covid/view/WorldMapContainer.mjs +4 -4
  7. package/apps/covid/view/country/Gallery.mjs +1 -1
  8. package/apps/covid/view/country/Helix.mjs +1 -1
  9. package/apps/covid/view/country/Table.mjs +27 -29
  10. package/apps/portal/index.html +1 -1
  11. package/apps/portal/resources/data/blog.json +12 -0
  12. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  13. package/apps/sharedcovid/view/GalleryContainer.mjs +1 -1
  14. package/apps/sharedcovid/view/HelixContainer.mjs +1 -1
  15. package/apps/sharedcovid/view/WorldMapContainer.mjs +4 -4
  16. package/apps/sharedcovid/view/country/Gallery.mjs +1 -1
  17. package/apps/sharedcovid/view/country/Helix.mjs +1 -1
  18. package/apps/sharedcovid/view/country/Table.mjs +22 -22
  19. package/examples/grid/bigData/ControlsContainer.mjs +14 -0
  20. package/examples/stateProvider/inline/MainContainer.mjs +1 -1
  21. package/examples/stateProvider/twoWay/MainContainer.mjs +2 -2
  22. package/examples/treeAccordion/MainContainer.mjs +1 -1
  23. package/learn/blog/v10-deep-dive-functional-components.md +107 -97
  24. package/learn/blog/v10-deep-dive-reactivity.md +3 -3
  25. package/learn/blog/v10-deep-dive-state-provider.md +42 -137
  26. package/learn/blog/v10-deep-dive-vdom-revolution.md +35 -61
  27. package/learn/blog/v10-post1-love-story.md +3 -3
  28. package/learn/gettingstarted/DescribingTheUI.md +108 -33
  29. package/learn/guides/fundamentals/ConfigSystemDeepDive.md +118 -18
  30. package/learn/guides/fundamentals/InstanceLifecycle.md +121 -84
  31. package/learn/tree.json +1 -0
  32. package/learn/tutorials/CreatingAFunctionalButton.md +179 -0
  33. package/package.json +3 -3
  34. package/src/DefaultConfig.mjs +2 -2
  35. package/src/button/Base.mjs +13 -4
  36. package/src/container/Base.mjs +9 -2
  37. package/src/data/Store.mjs +8 -3
  38. package/src/date/SelectorContainer.mjs +2 -2
  39. package/src/form/field/Base.mjs +15 -1
  40. package/src/form/field/ComboBox.mjs +5 -15
  41. package/src/functional/component/Base.mjs +26 -0
  42. package/src/functional/util/html.mjs +75 -0
  43. package/src/state/Provider.mjs +7 -4
  44. package/src/tree/Accordion.mjs +1 -1
  45. package/test/siesta/siesta.js +8 -1
  46. package/test/siesta/tests/form/field/AfterSetValueSequence.mjs +106 -0
  47. package/test/siesta/tests/functional/HtmlTemplateComponent.mjs +92 -0
  48. package/test/siesta/tests/state/FeedbackLoop.mjs +159 -0
  49. package/test/siesta/tests/state/Provider.mjs +56 -0
@@ -1,18 +1,17 @@
1
- # Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering
1
+ # The VDOM Revolution: How We Render UIs from a Web Worker
2
2
 
3
- In our previous deep dives, we've established the "why" and the "what" of Neo.mjs v10. We've seen how the
4
- **Two-Tier Reactivity System** creates a new reality for state management and how our new **Functional Components**
5
- provide a developer experience free from the "React tax."
3
+ The Virtual DOM is a cornerstone of modern frontend development. But what happens when you take this concept and move it
4
+ off the main thread entirely, into a Web Worker? It's a compelling idea—it promises a world where even the most complex
5
+ UI rendering and diffing can never block user interactions.
6
6
 
7
- Now, we arrive at the final piece of the puzzle: how do we get this hyper-efficient, off-thread application onto the screen?
8
- This is where we introduce **Act II: The VDOM Revolution**, an architectural leap that rethinks the very nature of
9
- rendering in a multi-threaded world.
10
-
11
- This revolution is built on two pillars:
12
- 1. **JSON Blueprints:** A more intelligent, efficient language for describing UIs.
13
- 2. **Asymmetric Rendering:** Using the right tool for the right job—a specialized renderer for initial insertions and a
14
- classic diffing engine for updates.
7
+ But this architectural shift introduces a new set of fascinating engineering challenges. How do you efficiently
8
+ communicate UI changes from a worker to the main thread? And what's the best language to describe a UI when it's being
9
+ built by a machine, for a machine?
15
10
 
11
+ This article explores the solutions to those problems, focusing on two key concepts:
12
+ 1. **JSON Blueprints:** Why using structured data is a more powerful way to define complex UIs than traditional HTML.
13
+ 2. **Asymmetric Rendering:** How using different, specialized strategies for creating new UI vs. updating existing UI
14
+ 3. leads to a more performant and secure system.
16
15
 
17
16
  *(Part 4 of 5 in the v10 blog series. Details at the bottom.)*
18
17
 
@@ -27,7 +26,7 @@ and enterprise dashboards—is sending pre-rendered HTML the ultimate endgame?
27
26
  We've seen this movie before. In the world of APIs, the verbose, heavyweight XML standard was supplanted by the lighter,
28
27
  simpler, and more machine-friendly JSON. We believe the same evolution is inevitable for defining complex UIs.
29
28
 
30
- Instead of the server laboring to render and stream HTML, Neo.mjs is built on the principle of **JSON Blueprints**.
29
+ Instead of the server laboring to render and stream HTML, Neo.mjs is built on the principle of **JSON Blueprints**.
31
30
  The server's job is to provide a compact, structured description of the component tree—its configuration, state, and
32
31
  relationships. Think of it as sending the architectural plans, not pre-fabricated walls.
33
32
 
@@ -35,14 +34,22 @@ This approach has profound advantages, especially for the AI-driven applications
35
34
 
36
35
  * **Extreme Data Efficiency:** A JSON blueprint is drastically smaller than its equivalent rendered HTML, minimizing data transfer.
37
36
  * **Server De-Loading:** This offloads rendering stress from the server, freeing it for core application logic and intensive AI computations.
38
- * **AI's Native Language:** This is the most critical advantage for the next generation of applications. An LLM's
39
- natural output is structured text. Asking it to generate a valid JSON object that conforms to a component's
37
+ * **AI's Native Language:** This is the most critical advantage for the next generation of applications.
38
+ An LLM's natural output is structured text. Asking it to generate a valid JSON object that conforms to a component's
40
39
  configuration is a far more reliable and constrained task than asking it to generate nuanced HTML with embedded logic
41
40
  and styles. The component's config becomes a clean, well-defined API for the AI to target, making UI generation less
42
41
  error-prone and more predictable.
43
42
  * **True Separation of Concerns:** The server provides the "what" (the UI blueprint); the client's worker-based engine
44
43
  expertly handles the "how" (rendering, interactivity, and state management).
45
44
 
45
+ This philosophy—that structured JSON is the future of UI definition—is not just a theoretical concept for us. It
46
+ is the core engine behind a new tool we are developing: **Neo Studio**. It's a multi-window, browser-based IDE
47
+ where we're integrating AI to generate component blueprints from natural language. The AI doesn't write JSX; it
48
+ generates the clean, efficient JSON that the framework then renders into a live UI. It's the first step towards
49
+ the vision of scaffolding entire applications this way.
50
+
51
+ `[Screenshot of the Neo Studio UI, showcasing a generated component from a prompt]`
52
+
46
53
  JSON blueprints are the language. Now let's look at the engine that translates them into a live application.
47
54
 
48
55
  ---
@@ -65,11 +72,9 @@ from the VDOM worker and uses the right tool for every job.
65
72
 
66
73
  #### For Creating New DOM: The `DomApiRenderer`
67
74
 
68
- Whenever a new piece of UI needs to be created, the VDOM worker sends an `insertNode` command.
69
- This isn't just for the initial page load. It applies any time you:
70
- - Dynamically add a new component to a container.
71
- - Or, in a capability that showcases the power of the multi-threaded architecture,
72
- move an entire component tree into a **new browser window**.
75
+ Whenever a new piece of UI needs to be created, the VDOM worker sends an `insertNode` command. This isn't just for the
76
+ initial page load. It applies any time you dynamically add a new component to a container or, in a capability that
77
+ showcases the power of the multi-threaded architecture, move an entire component tree into a **new browser window**.
73
78
 
74
79
  For all these creation tasks, our pipeline uses the `DomApiRenderer`. This renderer is not only fast but also
75
80
  **secure by default**. It never parses HTML strings, instead building the DOM programmatically with safe APIs like
@@ -106,9 +111,9 @@ performance, security, and flexibility.
106
111
  The other half of the revolution happens before an update is even sent. It’s about creating the smartest, most minimal
107
112
  blueprint possible.
108
113
 
109
- In v9, Neo.mjs already had a powerful solution for this: **Scoped VDOM Updates**. Using an `updateDepth` config, a parent
110
- container could intelligently send its own VDOM changes to the worker while treating its children as simple placeholders.
111
- This prevented wasteful VDOM diffing on child components that weren't part of the update.
114
+ In v9, Neo.mjs already had a powerful solution for this: **Scoped VDOM Updates**. Using an `updateDepth` config, a
115
+ parent container could intelligently send its own VDOM changes to the worker while treating its children as simple
116
+ placeholders. This prevented wasteful VDOM diffing on child components that weren't part of the update.
112
117
 
113
118
  However, this had a limitation. The `updateDepth` was an "all or nothing" switch for any given level of the component tree.
114
119
  Consider a toolbar with ten buttons. If the toolbar's own structure needed to change *and* just one of those ten buttons
@@ -117,14 +122,14 @@ also needed to update, the v9 model wasn't ideal.
117
122
  This is the exact challenge that **v10's Asymmetric Blueprints** were designed to solve.
118
123
 
119
124
  The new `VDomUpdate` manager and `TreeBuilder` utility work together to create a far more intelligent update payload.
120
- When the toolbar and one button need to change, the manager calculates the precise scope. The `TreeBuilder` then generates
121
- a partial VDOM blueprint that includes:
125
+ When the toolbar and one button need to change, the manager calculates the precise scope. The `TreeBuilder` then
126
+ generates a partial VDOM blueprint that includes:
122
127
  1. The full VDOM for the toolbar itself.
123
128
  2. The full VDOM for the *one* button that is changing.
124
129
  3. Lightweight `{componentId: 'neo-ignore'}` placeholders for the other nine buttons.
125
130
 
126
- The VDOM worker receives this highly optimized, asymmetric blueprint. When it sees a `neo-ignore` node, it completely
127
- skips diffing that entire branch of the UI.
131
+ The VDOM worker receives this highly optimized, asymmetric blueprint. When it sees a `neo-ignore` node,
132
+ it completely skips diffing that entire branch of the UI.
128
133
 
129
134
  It’s the ultimate optimization: instead of sending the entire blueprint for a skyscraper just to fix a window,
130
135
  we now send the floor plan for the lobby *and* the specific blueprint for that one window on the 50th floor,
@@ -133,37 +138,6 @@ and the framework automatically creates the most efficient update possible.
133
138
 
134
139
  ---
135
140
 
136
- #### 2. For Updates: From Scoped to Truly Asymmetric Blueprints
137
-
138
- Once a component is on the screen, we need to handle state changes with surgical precision. In v9, Neo.mjs already had
139
- a powerful solution for this: **Scoped VDOM Updates**. Using an `updateDepth` config, a parent container could
140
- intelligently send its own VDOM changes to the worker while treating its children as simple placeholders. This prevented
141
- wasteful VDOM diffing on child components that weren't part of the update.
142
-
143
- However, this had a limitation. The `updateDepth` was an "all or nothing" switch for any given level of the component tree.
144
- Consider a toolbar with ten buttons. If the toolbar's own structure needed to change *and* just one of those ten buttons
145
- also needed to update, the v9 model forced a choice: either send two separate, parallel updates (one for the toolbar,
146
- one for the button), or have the toolbar update its entire level, including the nine buttons that hadn't changed.
147
-
148
- This is the exact challenge that **v10's Asymmetric Blueprints** were designed to solve.
149
-
150
- The new `VDomUpdate` manager and `TreeBuilder` utility work together to create a far more intelligent update payload.
151
- When the toolbar and one button need to change, the manager calculates the precise scope. The `TreeBuilder` then generates
152
- a partial VDOM blueprint that includes:
153
- 1. The full VDOM for the toolbar itself.
154
- 2. The full VDOM for the *one* button that is changing.
155
- 3. Lightweight `{componentId: 'neo-ignore'}` placeholders for the other nine buttons.
156
-
157
- The VDOM worker receives this highly optimized, asymmetric blueprint. When it sees a `neo-ignore` node, it completely
158
- skips diffing that entire branch of the UI.
159
-
160
- It’s the ultimate optimization: instead of sending the entire blueprint for a skyscraper just to fix a window, we now
161
- send the floor plan for the lobby *and* the specific blueprint for that one window on the 50th floor, ignoring everything
162
- else in between. The worker focuses only on what matters, resulting in faster diffs and minimal data transfer between
163
- threads.
164
-
165
- ---
166
-
167
141
  ## Conclusion: An Engine Built for Tomorrow
168
142
 
169
143
  The VDOM Revolution in Neo.mjs isn't just a performance enhancement; it's a paradigm shift.
@@ -189,6 +163,6 @@ invite you to fall in love with frontend development all over again.
189
163
 
190
164
  1. [A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow](./v10-post1-love-story.md)
191
165
  2. [Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity](./v10-deep-dive-reactivity.md)
192
- 3. [Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
193
- 4. Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering
194
- 5. [Deep Dive: The State Provider Revolution](./v10-deep-dive-state-provider.md)
166
+ 3. [Designing Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
167
+ 4. The VDOM Revolution: How We Render UIs from a Web Worker
168
+ 5. [Designing a State Manager for Performance: A Deep Dive into Hierarchical Reactivity](./v10-deep-dive-state-provider.md)
@@ -322,9 +322,9 @@ It's time to fall in love with frontend again.
322
322
 
323
323
  1. A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow
324
324
  2. [Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity](./v10-deep-dive-reactivity.md)
325
- 3. [Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
326
- 4. [Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering](./v10-deep-dive-vdom-revolution.md)
327
- 5. [Deep Dive: The State Provider Revolution](./v10-deep-dive-state-provider.md)
325
+ 3. [Designing Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
326
+ 4. [The VDOM Revolution: How We Render UIs from a Web Worker](./v10-deep-dive-vdom-revolution.md)
327
+ 5. [Designing a State Manager for Performance: A Deep Dive into Hierarchical Reactivity](./v10-deep-dive-state-provider.md)
328
328
 
329
329
  ---
330
330
 
@@ -1,16 +1,54 @@
1
1
  # Describing a View
2
2
 
3
3
  A Neo.mjs view is comprised of components and containers. A component is a visual widget, like a button,
4
- and a container is a visual collection of components.
4
+ and a container is a visual collection of components.
5
5
 
6
- Neo.mjs is declarative, where your code _describes_ or _configures_ the things its creating.
6
+ Neo.mjs is declarative, where your code _describes_ or _configures_ the things it's creating.
7
7
 
8
- For example, if you wanted to create a button, you'd look in the API docs and see that buttons
9
- have a few key configs, including `text` and `iconCls`. The configs are properties you can
10
- use to describe the component you're creating> You can also access or set the properties dynamically.
8
+ There are two primary ways to describe a view: using modern **functional components** or classic **class-based components**.
9
+ Crucially, these two approaches are fully interoperable, allowing you to mix and match them to fit your needs.
11
10
 
11
+ ## The Modern Approach: Functional Components
12
12
 
13
- ## A view with one component
13
+ For most new views, especially those that are primarily presentational (like a button or a hero section), the recommended approach is to use functional components. This method is concise, highly performant, and aligns with modern reactive programming patterns.
14
+
15
+ Functional components are defined using the `defineComponent` helper. You provide a configuration object that includes a `createVdom` method. This method is a reactive function that returns the Virtual DOM (VDOM) for your component.
16
+
17
+ ### A simple functional view
18
+
19
+ Here is a simple view that displays a single button. The `createVdom` function returns a VDOM object that describes the button.
20
+
21
+ ```javascript live-preview
22
+ import {defineComponent} from '../functional/_export.mjs';
23
+
24
+ const MainView = defineComponent({
25
+ className: 'GS.describing.functional.MainView',
26
+
27
+ createVdom(config) {
28
+ return {
29
+ ntype: 'container',
30
+ layout: {ntype: 'vbox', align: 'start'},
31
+ items: [{
32
+ ntype: 'button',
33
+ iconCls: 'fa fa-home',
34
+ text: 'Home'
35
+ }]
36
+ }
37
+ }
38
+ });
39
+
40
+ export default MainView;
41
+ ```
42
+
43
+ ## The Classic Approach: Class-Based Components
44
+
45
+ For more complex, high-order components that require surgical precision and powerful state management (like a buffered grid, a calendar, or an image gallery), the classic class-based approach is more suitable.
46
+
47
+ This approach involves extending a framework class (like `Neo.container.Base`) and defining your view within the `static config` block. It gives you access to a rich set of lifecycle methods (`beforeSet...`, `afterSet...`, etc.) for fine-grained control.
48
+
49
+ ### A simple class-based view
50
+
51
+ Here is the same view, but built with the class-based approach.
14
52
 
15
53
  ```javascript live-preview
16
54
  import Button from '../button/Base.mjs';
@@ -18,7 +56,7 @@ import Container from '../container/Base.mjs';
18
56
 
19
57
  class MainView extends Container {
20
58
  static config = {
21
- className: 'GS.describing1.MainView',
59
+ className: 'GS.describing.class.MainView',
22
60
  layout : {ntype:'vbox', align:'start'},
23
61
  items : [{
24
62
  module : Button,
@@ -31,39 +69,71 @@ class MainView extends Container {
31
69
  MainView = Neo.setupClass(MainView);
32
70
  ```
33
71
 
72
+ ## Interoperability: The Best of Both Worlds
34
73
 
35
- The button config is just an object describing the button being created. In the example, it has three
36
- properties:
74
+ The power of Neo.mjs lies in its interoperability layer. You can seamlessly mix both component models.
37
75
 
38
- - `module` is the type of thing being created; it's the imported module name.
39
- - `text` is the button's text
40
- - `iconCls` is the css class used for the button's icon. Neo.mjs automatically includes Font Awesome,
41
- and `fa fa-home` matches a Font Awesome css class.
76
+ ### Using a Class-Based Component in a Functional View
42
77
 
43
- Components are almost always placed within a container. A container is a component that visually holds other
44
- components. Containers have an `items:[]` config, which is an array of the components within the container.
45
- Containers also have a `layout` property, which describes how the items are arranged.
78
+ You can easily instantiate a classic component within the `createVdom` method of a functional component. This is useful when you need to embed a complex, stateful widget inside a simpler, functional layout.
46
79
 
47
- Let's put a second button in the container.
80
+ ```javascript live-preview
81
+ import {defineComponent} from '../functional/_export.mjs';
82
+ import Calendar from '../calendar/Component.mjs'; // A complex, class-based component
83
+
84
+ const MainView = defineComponent({
85
+ className: 'GS.describing.interop.MainView1',
86
+
87
+ createVdom(config) {
88
+ return {
89
+ ntype: 'container',
90
+ layout: {ntype: 'vbox', align: 'start'},
91
+ items: [{
92
+ ntype: 'component',
93
+ vdom: {tag: 'h1', html: 'My Functional View'}
94
+ }, {
95
+ // Drop the class-based Calendar into our functional view
96
+ module: Calendar,
97
+ height: 300,
98
+ width: 300
99
+ }]
100
+ }
101
+ }
102
+ });
103
+
104
+ export default MainView;
105
+ ```
106
+
107
+ ### Using a Functional Component in a Class-Based View
48
108
 
49
- ## A view with two components
109
+ Conversely, you can drop a functional component into the `items` array of a classic container. This allows you to compose your complex views from smaller, more manageable functional pieces.
50
110
 
51
111
  ```javascript live-preview
52
- import Button from '../button/Base.mjs';
112
+ import {defineComponent} from '../functional/_export.mjs';
53
113
  import Container from '../container/Base.mjs';
54
114
 
115
+ // 1. Define a simple functional component
116
+ const MyFunctionalButton = defineComponent({
117
+ className: 'GS.describing.interop.FuncButton',
118
+ createVdom(config) {
119
+ return {
120
+ ntype: 'button',
121
+ iconCls: config.iconCls,
122
+ text: config.text
123
+ }
124
+ }
125
+ });
126
+
127
+ // 2. Create a class-based MainView
55
128
  class MainView extends Container {
56
129
  static config = {
57
- className: 'GS.describing2.MainView',
58
- layout : {ntype:'vbox', align:'start'}, // Change the ntype to 'hbox'
130
+ className: 'GS.describing.interop.MainView2',
131
+ layout : {ntype:'vbox', align:'start'},
59
132
  items : [{
60
- module : Button,
61
- iconCls: 'fa fa-home',
62
- text : 'Home'
63
- }, {
64
- module : Button,
65
- iconCls: 'fa fa-star',
66
- text : 'Star'
133
+ // 3. Use the functional component in the items array
134
+ module: MyFunctionalButton,
135
+ iconCls: 'fa fa-rocket',
136
+ text : 'Launch'
67
137
  }]
68
138
  }
69
139
  }
@@ -71,9 +141,14 @@ class MainView extends Container {
71
141
  MainView = Neo.setupClass(MainView);
72
142
  ```
73
143
 
74
- If you run the example you'll see two buttons, arranged according to the `layout`. If you'd like,
75
- modify the code to specify `ntype:'hbox'` and run it again.
144
+ ## Choosing Your Approach
145
+
146
+ * **Use Functional Components (`defineComponent`) for:**
147
+ * Simple, reusable components (e.g., buttons, chips, hero sections).
148
+ * Most of your application's views that are primarily presentational.
149
+ * When you want a concise, declarative, and highly performant component.
76
150
 
77
- Note that the layout specifies `ntype` rather than `module`. An `ntype` is an alias for a class
78
- that has already been imported. Containers import all the layout types, so since we've already
79
- imported container we can simply use `ntype` to specify which layout we want.
151
+ * **Use Class-Based Components (`class ... extends ...`) for:**
152
+ * Complex, high-order components requiring surgical precision (e.g., buffered grids, calendars, charts, galleries).
153
+ * Creating new reusable components that extend the functionality of existing framework classes.
154
+ * Components with intricate internal logic that benefits from the full range of class lifecycle methods.
@@ -4,13 +4,22 @@
4
4
  first to understand the foundational concepts and benefits.
5
5
 
6
6
  The Neo.mjs class configuration system is a cornerstone of the framework, providing a powerful, declarative, and
7
- reactive way to manage the state of your components and classes. Its internal mechanics are deeply intertwined with
8
- the instance lifecycle, ensuring predictable and consistent behavior. This guide will take you on a deep dive into
9
- how it achieves its remarkable consistency and power.
7
+ reactive way to manage the state of your components and classes. With the introduction of functional components in v10,
8
+ this system has evolved into a sophisticated, two-tier reactivity model that combines the robustness of a classic
9
+ "push" system with the fine-grained efficiency of a modern "pull" system.
10
10
 
11
- ## 1. Core Concepts Recap
11
+ This guide will take you on a deep dive into how this hybrid system works, giving you the knowledge to build highly
12
+ performant and maintainable applications.
12
13
 
13
- At its heart, the config system is built on a few key principles:
14
+ ## Tier 1: The Classic "Push" System
15
+
16
+ The original reactivity model in Neo.mjs is a "push" system. It's imperative, meaning that when you change a value,
17
+ the system actively "pushes" that change through a series of predefined lifecycle hooks. This system remains a core
18
+ part of v10 and is the foundation for class-based components.
19
+
20
+ ### 1. Core Concepts Recap
21
+
22
+ At its heart, the push system is built on a few key principles:
14
23
 
15
24
  * **`static config` Block:** All configurable properties of a class are declared in a `static config = {}` block.
16
25
  This provides a single, clear source of truth for a class's API.
@@ -26,13 +35,13 @@ At its heart, the config system is built on a few key principles:
26
35
  automatically runs whenever a specific config property changes, ensuring your UI and application state are always
27
36
  in sync.
28
37
 
29
- ## 2. The Internal Mechanics: `set()`, `processConfigs()`, and `configSymbol`
38
+ ### 2. The Internal Mechanics: `set()`, `processConfigs()`, and `configSymbol`
30
39
 
31
40
  To truly understand how Neo.mjs handles complex scenarios like simultaneous updates and inter-dependencies, we must
32
41
  look at the internal machinery: the `set()` and `processConfigs()` methods in `Neo.core.Base`, and the special
33
42
  `configSymbol` object.
34
43
 
35
- ### The `set()` Method: Your Gateway to Updates
44
+ #### The `set()` Method: Your Gateway to Updates
36
45
 
37
46
  The `set()` method is the public interface for changing one or more config properties at once. When you call
38
47
  `this.set({a: 1, b: 2})`, you kick off a carefully orchestrated sequence.
@@ -68,7 +77,7 @@ Here’s the breakdown:
68
77
  from `configSymbol` to the actual instance properties. The `true` argument (`forceAssign`) is crucial, as we'll
69
78
  see next.
70
79
 
71
- ### The `processConfigs()` Method: The Heart of the Operation
80
+ #### The `processConfigs()` Method: The Heart of the Operation
72
81
 
73
82
  This internal method iteratively processes the configs stored in `configSymbol`. It's designed as a recursive
74
83
  function to handle the dynamic nature of config processing, where one `afterSet` might trigger another `set()`.
@@ -108,7 +117,7 @@ processConfigs(forceAssign=false) {
108
117
  has been invoked. This is vital to prevent reprocessing and to mark the config as handled.
109
118
  * **Recursion (E):** The method calls itself to process the next item in `configSymbol` until it's empty.
110
119
 
111
- ## 3. Solving the "Circular Reference" Problem
120
+ ### 3. Solving the "Circular Reference" Problem
112
121
 
113
122
  What happens when two `afterSet` methods depend on each other's properties?
114
123
 
@@ -174,10 +183,98 @@ Here's the sequence:
174
183
  operation. This guarantees that all `afterSet` handlers, regardless of their execution order, operate on the most
175
184
  current and consistent state of all config properties involved in that operation.
176
185
 
177
- ## 4. In-depth Example: A Reactive `MainContainer`
186
+ ## Tier 2: The Declarative "Pull" System (v10+)
187
+
188
+ With the introduction of functional components, Neo.mjs now includes a "pull" reactivity system. This system is
189
+ declarative and optimized for fine-grained updates, making it ideal for modern, state-driven UI development. Instead
190
+ of "pushing" changes through hooks, the system "pulls" data as needed, automatically tracking dependencies and
191
+ re-running computations only when necessary.
192
+
193
+ This tier is powered by three key classes: `Neo.core.Config`, `Neo.core.Effect`, and `EffectManager`.
194
+
195
+ ### 1. `Neo.core.Config`: The Atomic Unit of State
196
+
197
+ A `Neo.core.Config` instance is a lightweight wrapper around a single, reactive piece of data. Think of it as an
198
+ "atom" of state.
199
+
200
+ * **Value Storage:** It holds the current value of a config property.
201
+ * **Subscription Management:** It maintains a list of subscribers (effects or other logic) that depend on its value.
202
+ * **Dependency Tracking:** When its `get()` method is called within a reactive context (an "effect"), it registers
203
+ itself as a dependency of that effect.
204
+ * **Notification:** When its `set()` method is called and the value changes, it notifies all its subscribers,
205
+ triggering them to re-run.
206
+
207
+ ### 2. `Neo.core.Effect`: The Reactive Computation
208
+
209
+ An `Neo.core.Effect` represents a reactive computation—a function that depends on one or more `Config` atoms.
210
+
211
+ * **Wrapping a Function:** It wraps a function (e.g., a component's rendering logic).
212
+ * **Automatic Dependency Tracking:** When the effect runs, it automatically detects which `Config` atoms are `get()`
213
+ inside its function. It then subscribes to them.
214
+ * **Automatic Re-execution:** If any of its dependencies change (i.e., their `set()` method is called), the effect's
215
+ function is automatically re-executed, ensuring the computation is always up-to-date.
216
+
217
+ ### 3. `EffectManager`: The Global Coordinator
218
+
219
+ The `EffectManager` is a singleton that orchestrates the entire pull system.
220
+
221
+ * **Effect Stack:** It maintains a stack of currently running effects. This is how a `Config` atom knows which effect
222
+ to register itself with when its `get()` method is called.
223
+ * **Batching:** It provides `Neo.batch()`, a crucial optimization function. It allows the system to pause effect
224
+ re-runs, perform multiple state changes, and then resume, running all affected effects only once at the end. This
225
+ prevents "glitches" and unnecessary intermediate computations.
226
+
227
+ ### 4. `createVdom` as a Master Effect
228
+
229
+ In a functional component, the `createVdom()` method is the perfect example of an effect in action.
230
+
231
+ * **The `vdomEffect`:** When a functional component is constructed, the framework automatically wraps its `createVdom()`
232
+ method in a `Neo.core.Effect`.
233
+ * **Reading is Subscribing:** When your `createVdom()` function runs, every component config you access (e.g.,
234
+ `config.text`, `config.items`) is a call to that config's underlying `Neo.core.Config` atom's `get()` method.
235
+ This automatically subscribes the `vdomEffect` to those configs.
236
+ * **Automatic UI Updates:** If any of those configs change later, they notify the `vdomEffect`. The effect then
237
+ re-runs your `createVdom()` function, generating a new virtual DOM based on the new state. The framework then
238
+ efficiently diffs this new VDOM with the old one and applies the minimal necessary changes to the actual DOM.
239
+
240
+ This is the essence of declarative, state-driven UI. You declare what the UI should look like for a given state, and
241
+ the framework handles the "how" and "when" of updating it.
242
+
243
+ ## The Bridge: How "Push" and "Pull" Work Together
244
+
245
+ The true power of the v10 config system is how these two tiers are seamlessly integrated. This bridge is forged in
246
+ the auto-generated setters of reactive configs and the `set()` method of `Neo.core.Base`.
247
+
248
+ When you change a config on any component (class-based or functional):
249
+
250
+ ```javascript readonly
251
+ myComponent.myConfig = 'new value';
252
+ // or
253
+ myComponent.set({myConfig: 'new value'});
254
+ ```
255
+
256
+ Here's what happens under the hood:
257
+
258
+ 1. **Batching Begins:** The `set()` method in `Neo.core.Base` immediately calls `EffectManager.pause()`. This tells
259
+ the "pull" system to queue up any effects that get triggered but not to run them yet.
260
+ 2. **The Setter is Called:** The auto-generated setter for `myConfig` is invoked. This setter is the heart of the
261
+ bridge. It performs two critical actions:
262
+ * **Pull System Update:** It retrieves the `Neo.core.Config` instance for `myConfig` (using `this.getConfig('myConfig')`)
263
+ and calls its `set()` method with the new value. This updates the reactive atom and queues any dependent effects
264
+ (like a functional component's `vdomEffect`).
265
+ * **Push System Update:** It proceeds with the classic "push" system logic, adding the new value to the
266
+ `configSymbol` staging area and eventually calling the `afterSetMyConfig()` hook.
267
+ 3. **Batching Ends:** After the `set()` method in `core.Base` has finished processing all configs in the batch, its
268
+ `finally` block calls `EffectManager.resume()`. This tells the `EffectManager` to run all the unique effects that
269
+ were queued during the operation, ensuring that the UI and other reactive computations update exactly once.
270
+
271
+ This elegant integration means you get the best of both worlds: the predictable, hook-based logic of the push system
272
+ and the automatic, fine-grained reactivity of the pull system, all working in harmony.
273
+
274
+ ## In-depth Example: A Reactive `MainContainer`
178
275
 
179
276
  Let's analyze a practical example to see these concepts in action. The `Neo.examples.core.config.MainContainer`
180
- demonstrates how to build a reactive UI declaratively.
277
+ demonstrates how to build a reactive UI declaratively using the classic "push" system.
181
278
 
182
279
  **The Goal:** Create a container with two labels. The text of each label is calculated based on the values of two
183
280
  config properties, `a` and `b`. A button allows the user to change `a` and `b` simultaneously.
@@ -259,23 +356,26 @@ class MainContainer extends Viewport {
259
356
  * `afterSetB` runs. It calculates `label2.text` as `value (10) + this.a (reads 10 from _a) = 20`.
260
357
  * **New State:** `label1` shows "20", `label2` shows "20".
261
358
 
262
- This example vividly demonstrates the dynamic and reactive nature of the system, where a single declarative state
263
- change automatically propagates through the component logic.
359
+ This example vividly demonstrates the dynamic and reactive nature of the "push" system, where a single declarative state
360
+ change automatically propagates through the component logic via `afterSet` hooks.
264
361
 
265
- ## 5. Best Practices
362
+ ## Best Practices for the Hybrid System
266
363
 
267
- * **Embrace Declarativity:** Define your entire UI structure inside `static config` whenever possible. This improves
268
- readability and maintainability.
364
+ * **Embrace Declarativity:** For functional components, define your UI structure inside `createVdom`. Trust the
365
+ reactive system to handle updates. For class-based components, define your entire UI structure inside `static config`
366
+ whenever possible. This improves readability and maintainability.
269
367
  * **Use the `_` Suffix Wisely:** Only add the trailing underscore to configs that need `afterSet`, `beforeSet` or
270
368
  `beforeGet` based logic. For simple value properties, omit it to avoid unnecessary overhead.
271
369
  * **Keep `afterSet` Handlers Pure:** An `afterSet` handler should ideally only react to the change of its own
272
370
  property and update other parts of the application. Avoid triggering complex chains of `set()` calls from within
273
371
  an `afterSet` if possible.
274
372
  * **Batch Updates with `set()`:** When you need to change multiple properties at once, always use a single
275
- `set({a: 1, b: 2})` call. This is more efficient and ensures consistency, as demonstrated above.
373
+ `set({a: 1, b: 2})` call. This is more efficient and ensures consistency across both reactivity systems.
276
374
  * **Use `onConstructed` for Post-Construction Logic:** Use the `onConstructed` lifecycle method to perform any setup
277
375
  that depends on the instance's initial configuration being fully processed. This is the ideal place for logic that
278
376
  requires all configs to be set and potentially other instances to be created (if set-driven).
377
+ * **Understand Your Dependencies:** Be mindful of which configs you access inside `createVdom` and other effects, as
378
+ this determines when they will re-run.
279
379
 
280
380
  By understanding these internal mechanics and following best practices, you can leverage the full power of Neo.mjs's
281
- class config system to build highly complex, reactive, and maintainable applications with confidence.
381
+ hybrid config system to build highly complex, reactive, and maintainable applications with confidence.