neo.mjs 10.1.0 → 10.2.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 (43) hide show
  1. package/.github/RELEASE_NOTES/v10.1.1.md +13 -0
  2. package/.github/RELEASE_NOTES/v10.2.0.md +34 -0
  3. package/ServiceWorker.mjs +2 -2
  4. package/apps/covid/view/country/Gallery.mjs +1 -1
  5. package/apps/covid/view/country/Helix.mjs +1 -1
  6. package/apps/covid/view/country/Table.mjs +27 -29
  7. package/apps/portal/index.html +1 -1
  8. package/apps/portal/resources/data/blog.json +12 -0
  9. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  10. package/apps/sharedcovid/view/country/Gallery.mjs +1 -1
  11. package/apps/sharedcovid/view/country/Helix.mjs +1 -1
  12. package/apps/sharedcovid/view/country/Table.mjs +22 -22
  13. package/examples/grid/bigData/ControlsContainer.mjs +14 -0
  14. package/examples/stateProvider/inline/MainContainer.mjs +1 -1
  15. package/examples/stateProvider/twoWay/MainContainer.mjs +2 -2
  16. package/examples/treeAccordion/MainContainer.mjs +1 -1
  17. package/learn/blog/v10-deep-dive-functional-components.md +107 -97
  18. package/learn/blog/v10-deep-dive-reactivity.md +3 -3
  19. package/learn/blog/v10-deep-dive-state-provider.md +42 -137
  20. package/learn/blog/v10-deep-dive-vdom-revolution.md +35 -61
  21. package/learn/blog/v10-post1-love-story.md +3 -3
  22. package/learn/gettingstarted/DescribingTheUI.md +108 -33
  23. package/learn/guides/fundamentals/ConfigSystemDeepDive.md +118 -18
  24. package/learn/guides/fundamentals/InstanceLifecycle.md +121 -84
  25. package/learn/tree.json +1 -0
  26. package/learn/tutorials/CreatingAFunctionalButton.md +179 -0
  27. package/package.json +3 -3
  28. package/src/DefaultConfig.mjs +2 -2
  29. package/src/collection/Base.mjs +8 -3
  30. package/src/data/Store.mjs +8 -3
  31. package/src/date/SelectorContainer.mjs +2 -2
  32. package/src/form/field/Base.mjs +15 -1
  33. package/src/form/field/ComboBox.mjs +5 -15
  34. package/src/functional/component/Base.mjs +26 -0
  35. package/src/functional/util/html.mjs +75 -0
  36. package/src/state/Provider.mjs +7 -4
  37. package/src/tree/Accordion.mjs +1 -1
  38. package/test/siesta/siesta.js +8 -1
  39. package/test/siesta/tests/CollectionBase.mjs +46 -0
  40. package/test/siesta/tests/form/field/AfterSetValueSequence.mjs +106 -0
  41. package/test/siesta/tests/functional/HtmlTemplateComponent.mjs +92 -0
  42. package/test/siesta/tests/state/FeedbackLoop.mjs +159 -0
  43. package/test/siesta/tests/state/Provider.mjs +56 -0
@@ -1,28 +1,27 @@
1
- # Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World
1
+ # Designing Functional Components for a Multi-Threaded World
2
2
 
3
- If you're a seasoned React developer, you've mastered the art of hooks. You know how to build complex, stateful UIs with
4
- `useState`, `useEffect`, and `useMemo`. You also know the trade-offs—the intricate dependency arrays, the constant battle
5
- against unnecessary re-renders, and the "memoization tax" you pay to keep your application performant.
3
+ ## Say Goodbye to Manual Memoization: How a New Architecture Makes Performance a Feature, Not a Chore.
6
4
 
7
- We've been taught that these trade-offs are fundamental to the functional component model. But what if they aren't?
8
- What if they are merely symptoms of a single-threaded architecture?
5
+ Functional components have become a standard for building UIs, but they often come with a hidden cost: a constant, manual
6
+ effort to manage performance. We've learned to fight unnecessary re-renders with memoization hooks and complex dependency
7
+ arrays. But what if these weren't fundamental trade-offs? What if they were symptoms of an architecture that binds our UI
8
+ logic to a single thread?
9
9
 
10
- This article will show you a new breed of functional component, born from a multi-threaded world, that eliminates these
11
- compromises by design. We didn't build them to copy other frameworks; we built them because our architecture unlocked a
12
- better way to write UIs.
10
+ This is an exploration into a different kind of functional component—one designed from the ground up for a multi-threaded
11
+ world, where performance is a feature of the architecture, not a burden on the developer.
13
12
 
14
13
  *(Part 3 of 5 in the v10 blog series. Details at the bottom.)*
15
14
 
16
- ### A First Look: The Anatomy of a Neo.mjs Functional Component
15
+ ### A First Look: The Anatomy of a Multi-Threaded Functional Component
17
16
 
18
- Before we dive into the "why," let's look at the "what." Here is a simple, complete, and reactive functional component
19
- in Neo.mjs. Keep this structure in mind as we explore how it solves the problems you've come to accept as normal.
17
+ Before we dive into the "why," let's look at the "what." Here is a simple, complete, and reactive functional component in
18
+ Neo.mjs. Keep this structure in mind as we explore how it solves common performance problems at an architectural level.
20
19
 
21
20
  ```javascript
22
21
  import {defineComponent, useConfig, useEvent} from 'neo.mjs';
23
22
 
24
23
  export default defineComponent({
25
- // The component's public API (like props)
24
+ // The component's public API
26
25
  config: {
27
26
  className: 'My.Counter',
28
27
  labelText_: 'Counter:'
@@ -30,7 +29,7 @@ export default defineComponent({
30
29
 
31
30
  // The function that creates the VDOM
32
31
  createVdom(config) {
33
- // Internal, private state (like useState)
32
+ // Internal, private state
34
33
  const [count, setCount] = useConfig(0);
35
34
 
36
35
  // An event listener that updates the private state
@@ -47,22 +46,26 @@ export default defineComponent({
47
46
  });
48
47
  ```
49
48
 
50
- Notice what's missing: there are no dependency arrays, no `memo` wrappers, and no `useCallback` hooks. Now, let's explore
51
- the architecture that makes this clean, simple code possible.
49
+ Notice what's missing: there are no dependency arrays and no manual memoization wrappers. Now, let's explore the
50
+ architecture that makes this clean, simple code possible.
52
51
 
53
52
  ---
54
53
 
55
54
  ### The Architectural Divide: Why Your Component's Environment Matters
56
55
 
57
- Before we deconstruct the problems, we have to address the fundamental difference that changes everything. In a
58
- traditional framework like React, your component function, its state, its reconciliation (diffing), and its DOM
59
- manipulation all happen on the **same main thread** that is responsible for user interactions.
56
+ The key to this new model is a fundamental shift in where your code runs. In a traditional single-threaded framework,
57
+ your component logic, state management, VDOM diffing, and DOM manipulation all compete for resources on the
58
+ **same main thread** that handles user interactions.
60
59
 
61
60
  In Neo.mjs, the architecture is fundamentally different:
62
61
 
63
62
  1. **Your Application Logic (including your component's `createVdom` function) runs in a dedicated App Worker.**
64
63
  2. **The VDOM diffing happens in a separate VDom Worker.**
65
64
  3. **The Main Thread is left with one primary job: applying the calculated DOM patches.**
65
+ While this process leverages browser primitives like `requestAnimationFrame` for optimal visual synchronization,
66
+ it's crucial to understand that *only* the final, highly optimized DOM updates occur here. Your application logic
67
+ and VDOM calculations are entirely offloaded to workers, preventing main thread contention and ensuring a consistently
68
+ smooth user experience.
66
69
 
67
70
  This isn't just a minor difference; it's a paradigm shift. Your component code is decoupled from the rendering engine,
68
71
  which allows for a level of performance and predictability that is architecturally impossible on the main thread.
@@ -70,64 +73,76 @@ With this in mind, let's see how this new architecture solves old problems.
70
73
 
71
74
  ---
72
75
 
73
- ### Deconstructing the "React Tax": How a New Architecture Solves Old Problems
76
+ ### Solving Core Performance Challenges Architecturally
74
77
 
75
- Let's tackle the compromises you've learned to live with, one by one, and show how a multi-threaded architecture solves
76
- them at their root.
78
+ Let's tackle the performance compromises you've learned to live with, one by one, and show how a multi-threaded
79
+ architecture solves them at their root.
77
80
 
78
- #### 1. The Problem: Cascading Re-Renders & The `memo` Tax
81
+ #### 1. The Challenge: Cascading Updates
79
82
 
80
- **The React Way:** You know the drill. A state change in a parent component triggers a re-render. By default, React then
81
- re-renders **all of its children**, whether their props have changed or not. To prevent this performance drain,
82
- you are forced to pay the `memo` tax: wrapping components in `React.memo()`, manually memoizing functions with
83
- `useCallback()`, and objects with `useMemo()`. This manual optimization becomes a core, and often frustrating,
84
- part of the development process.
85
-
86
- ```javascript
87
- // A typical "optimized" React component
88
- const MyComponent = React.memo(({ onButtonClick, user }) => {
89
- console.log('Rendering MyComponent');
90
- return <button onClick={onButtonClick}>{user.name}</button>;
91
- });
92
-
93
- const App = () => {
94
- const [count, setCount] = useState(0);
95
-
96
- // We must wrap this in useCallback to prevent MyComponent from re-rendering
97
- // every time the App component's state changes.
98
- const handleClick = useCallback(() => {
99
- console.log('Button clicked!');
100
- }, []);
101
-
102
- // We must wrap this in useMemo to ensure the object reference is stable.
103
- const user = useMemo(() => ({ name: 'John Doe' }), []);
104
-
105
- return (
106
- <div>
107
- <button onClick={() => setCount(c => c + 1)}>App Clicks: {count}</button>
108
- <MyComponent onButtonClick={handleClick} user={user} />
109
- </div>
110
- );
111
- };
112
- ```
83
+ In many component-based systems, a state change in a parent component triggers a re-render. By default, this often
84
+ re-renders **all of its children**, whether their own inputs have changed or not. To prevent this performance drain,
85
+ developers are forced into manual optimization. This often involves wrapping components in memoization functions and
86
+ carefully managing the referential stability of callbacks and object props. This manual work becomes a core, and often
87
+ frustrating, part of the development process.
113
88
 
114
89
  **The Neo.mjs Solution: Surgical Effects, Not Brute-Force Renders**
115
90
 
116
91
  Our `createVdom` method is a surgical `Effect`. It automatically and precisely tracks every piece of reactive state it reads.
117
- When a piece of state changes, **only the specific `createVdom` effects that depend on that exact piece of state are queued
118
- for re-execution.**
92
+ When a piece of state changes, **only the specific `createVdom` effects that depend on that exact piece of state are
93
+ queued for re-execution.**
119
94
 
120
95
  There are no cascading re-renders. If a parent's `createVdom` re-runs, but the configs passed to a child have not changed,
121
- the child component's `createVdom` function is **never executed**.
96
+ the child component's `createVdom` function is **never executed**.
97
+
98
+ This efficiency extends to child components. On the first execution cycle, when a child component is encountered in the VDOM,
99
+ Neo.mjs creates a new instance of that child component. In all subsequent renders, if the child component is still present,
100
+ Neo.mjs *retains* the existing instance. Instead of re-executing the child's `createVdom` or re-creating its entire VDOM,
101
+ Neo.mjs employs a sophisticated `diffAndSet()` mechanism. This process surgically compares the new configuration (props)
102
+ intended for the child with its last applied configuration. Only if actual changes are detected are the corresponding
103
+ `set()` methods invoked on the child component's instance. This triggers a highly localized, scoped VDOM update within
104
+ that child, ensuring that only the truly affected parts of the UI are re-rendered, even for deeply nested components.
105
+
106
+ To prove this, consider this example:
107
+ ```javascript
108
+ import {defineComponent, useConfig} from 'neo.mjs/src/functional/_export.mjs';
109
+
110
+ const ChildComponent = defineComponent({
111
+ createVdom(config) {
112
+ // This log is our proof. It will only fire when this
113
+ // specific component's render logic is executed.
114
+ console.log('Rendering ChildComponent');
115
+ return {tag: 'div', text: 'I am the child'};
116
+ }
117
+ });
118
+
119
+ export default defineComponent({
120
+ createVdom() {
121
+ const [count, setCount] = useConfig(0);
122
122
 
123
- This means `memo`, `useCallback`, and `useMemo` are not needed. The architecture is efficient by default, eliminating an
124
- entire class of performance optimizations and bugs.
123
+ return {
124
+ cn: [{
125
+ tag: 'button',
126
+ onclick: () => setCount(prev => prev + 1),
127
+ text: `Parent Clicks: ${count}`
128
+ }, {
129
+ // The child component receives no props that change
130
+ // when the parent's internal 'count' state changes.
131
+ module: ChildComponent
132
+ }]
133
+ }
134
+ }
135
+ });
136
+ ```
137
+ > When you run this code and click the button, you will see the "Parent Clicks" count update in the UI, but the
138
+ > "Rendering ChildComponent" message will only appear in the console **once**. This demonstrates that the parent's state
139
+ > change did not trigger a re-render of the child, proving the efficiency of the architecture.
125
140
 
126
- #### 2. The Problem: The Boilerplate of Immutability
141
+ #### 2. The Challenge: The Boilerplate of Immutability
127
142
 
128
- **The React Way:** To change a nested object in React state, you have to meticulously reconstruct the object path with
129
- spread syntax (`...`), creating new references for every level. This is required to signal to React's diffing algorithm
130
- that something has changed.
143
+ To signal that a state change has occurred, many frameworks require developers to treat state as immutable. To change a
144
+ nested object, you have to meticulously reconstruct the object path, creating new references for every level. While this
145
+ makes the change detection algorithm simpler for the framework, it offloads significant cognitive burden onto the developer.
131
146
 
132
147
  ```javascript
133
148
  // The familiar immutable update dance
@@ -143,7 +158,7 @@ setState(prevState => ({
143
158
  }));
144
159
  ```
145
160
 
146
- **The Neo.mjs Solution: Mutability for You, Immutability for the Machine**
161
+ **The Neo.mjs Solution:** Mutability for You, Immutability for the Machine
147
162
 
148
163
  We believe the developer should not carry this cognitive load. In Neo.mjs, you can just mutate the state directly.
149
164
  It's simple and intuitive.
@@ -157,12 +172,12 @@ How does it work? When an update is triggered, the framework handles creating an
157
172
  the diffing process in the VDom Worker. We provide the best of both worlds: simple, direct mutation for the developer
158
173
  and a safe, immutable structure for the high-performance diffing algorithm.
159
174
 
160
- #### 3. The Problem: The "SSR and Hydration is the ONLY way" Mindset
175
+ #### 3. The Challenge: The "Hydration is the Only Way" Mindset
161
176
 
162
- **The React/Next.js Way:** The industry has invested heavily in Server-Side Rendering and hydration to improve perceived
163
- performance and SEO. For content-heavy sites, this is a valid strategy. But for complex, stateful Single-Page Applications,
164
- it introduces immense complexity: the "hydration crisis," the difficulty of managing server vs. client components, and
165
- the fact that after all that work, your application is *still* running on the client's main thread.
177
+ The industry has invested heavily in Server-Side Rendering and hydration to improve perceived performance and SEO.
178
+ For content-heavy sites, this is a valid strategy. But for complex, stateful Single-Page Applications, it introduces
179
+ immense complexity: the "hydration crisis," the difficulty of managing server vs. client components, and the fact that
180
+ after all that work, your application is *still* running on the client's main thread.
166
181
 
167
182
  **The Neo.mjs Solution: Blueprints, Not Dehydrated HTML**
168
183
 
@@ -173,13 +188,20 @@ complex applications that are not primarily focused on static content.
173
188
 
174
189
  ---
175
190
 
176
- ### The "WOW" Effect: Building a Real Application
191
+ ### Building a Real Application
177
192
 
178
193
  The simple counter example is a great start, but the true power of functional components is revealed when you build a
179
194
  complete, interactive application. Let's build a slightly more advanced "Task List" application to demonstrate how all
180
195
  the pieces come together.
181
196
 
182
197
  This example will showcase:
198
+ - **Full Interoperability:** Neo.mjs embraces both functional and class-based (OOP) component paradigms as first-class
199
+ citizens, offering unparalleled flexibility. For developers familiar with the functional style, our functional
200
+ components provide a natural and intuitive starting point within Neo.mjs, allowing them to leverage a familiar approach.
201
+ Regardless of your preferred paradigm, you can seamlessly integrate functional components into traditional OOP containers,
202
+ and conversely, directly embed class-based components within the declarative VDOM of a functional component.
203
+ This architectural strength empowers developers to choose the most appropriate style for each part of their application,
204
+ or combine them as needed, without compromise.
183
205
  - **Component Composition:** Using a class-based `List` component within our functional view.
184
206
  - **State Management:** Tracking the currently selected task.
185
207
  - **Conditional Rendering:** Displaying task details only when a task is selected.
@@ -204,8 +226,7 @@ const TaskStore = Neo.create(Store, {
204
226
  // 2. Define our main application view
205
227
  export default defineComponent({
206
228
  config: {
207
- className: 'My.TaskListApp',
208
- layout: {ntype: 'hbox', align: 'stretch'}
229
+ className: 'My.TaskListApp'
209
230
  },
210
231
  createVdom() {
211
232
  // 3. Manage the selected task with useConfig
@@ -251,36 +272,25 @@ export default defineComponent({
251
272
  ```
252
273
 
253
274
  This single `defineComponent` call creates a fully-featured application. Notice how the `createVdom` function is a pure,
254
- declarative representation of the UI. When the `selectedTask` state changes, the framework surgically re-renders only
255
- the details pane. This is the power of the new component model in action: complex, stateful, and performant applications
256
- with beautifully simple code.
257
-
258
- ---
259
-
260
- ### The AI Connection: The Inevitable Next Step
261
-
262
- This blueprint-first, surgically reactive, and mutable-by-default model isn't just better for you; it's the architecture
263
- an AI would choose. An AI can easily generate and manipulate a structured JSON blueprint, but it struggles to generate
264
- flawless, complex JSX. By building on these principles, you are not just using a new framework; you are future-proofing
265
- your skills for the AI era.
275
+ declarative representation of the UI. When the `selectedTask` state changes, the framework surgically re-renders only the
276
+ details pane. This is the power of the new component model in action: complex, stateful, and performant applications with
277
+ beautifully simple code.
266
278
 
267
279
  ---
268
280
 
269
281
  ### Conclusion: A Different Philosophy, A Better Component
270
282
 
271
- Neo.mjs functional components are not a "React clone." They are a re-imagining of what a functional component can be
272
- when freed from the architectural constraints of the main thread. They offer a development experience that is not only
273
- more performant by default but also simpler, more intuitive, and ready for the AI-driven future of the web.
283
+ Neo.mjs functional components are a re-imagining of what a functional component can be when freed from the architectural
284
+ constraints of the main thread. They offer a development experience that is not only more performant by default but also
285
+ simpler and more intuitive.
274
286
 
275
287
  This is what it feels like to stop paying the performance tax and start building again.
276
288
 
277
289
  The clean, hook-based API for functional components is possible because it stands on the shoulders of a robust, modular,
278
- and deeply integrated class system. We've engineered the framework's core to handle the complex machinery of reactivity
279
- and lifecycle management automatically. To learn more about the powerful engine that makes this all possible, see our
280
- deep dive on the **Two-Tier Reactivity System**.
290
+ and deeply integrated class config system. To learn more about the powerful engine that makes this all possible, see our deep
291
+ dive on the **Two-Tier Reactivity System**.
281
292
 
282
- Next, we will look at how this architecture revolutionizes the very way we render UIs with the Asymmetric VDOM
283
- and JSON Blueprints.
293
+ Next, we will look at how this architecture revolutionizes the very way we render UIs with the Asymmetric VDOM and JSON Blueprints.
284
294
 
285
295
  ---
286
296
 
@@ -288,6 +298,6 @@ and JSON Blueprints.
288
298
 
289
299
  1. [A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow](./v10-post1-love-story.md)
290
300
  2. [Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity](./v10-deep-dive-reactivity.md)
291
- 3. Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World
292
- 4. [Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering](./v10-deep-dive-vdom-revolution.md)
293
- 5. [Deep Dive: The State Provider Revolution](./v10-deep-dive-state-provider.md)
301
+ 3. Designing Functional Components for a Multi-Threaded World
302
+ 4. [The VDOM Revolution: How We Render UIs from a Web Worker](./v10-deep-dive-vdom-revolution.md)
303
+ 5. [Designing a State Manager for Performance: A Deep Dive into Hierarchical Reactivity](./v10-deep-dive-state-provider.md)
@@ -517,6 +517,6 @@ doesn't just simplify your code — it makes entirely new patterns of developmen
517
517
 
518
518
  1. [A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow](./v10-post1-love-story.md)
519
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)
520
+ 3. [Designing Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
521
+ 4. [The VDOM Revolution: How We Render UIs from a Web Worker](./v10-deep-dive-vdom-revolution.md)
522
+ 5. [Designing a State Manager for Performance: A Deep Dive into Hierarchical Reactivity](./v10-deep-dive-state-provider.md)
@@ -1,55 +1,31 @@
1
- # Deep Dive: The State Provider Revolution
1
+ # Designing a State Manager for Performance: A Deep Dive into Hierarchical Reactivity
2
2
 
3
- **Subtitle: How Neo.mjs Delivers Intuitive State Management Without the Performance Tax**
3
+ Every application developer knows the pain of state management. You have a piece of state—a user object, a theme setting—that needs to be accessed by a component buried deep within your UI tree. The traditional approach is "prop-drilling": passing that data down through every single intermediate component. It's tedious, error-prone, and creates a tight coupling between components that shouldn't know about each other.
4
4
 
5
- In our "Three-Act Revolution" series, we've explored the high-level concepts of Neo.mjs v10. Now, it's time to dive deep
6
- into the technology that powers **Act I: The Reactivity Revolution**. We'll explore the new `state.Provider`, a system
7
- designed to solve one of the most persistent challenges in application development: managing shared state.
5
+ Modern frameworks solve this with a "Context API," a central provider that makes state available to any descendant. While this solves prop-drilling, it often introduces a hidden performance penalty. In many implementations, when *any* value in the context changes, *all* components consuming that context are forced to re-render, even if they don't care about the specific piece of data that changed.
8
6
 
9
- *(Part 5 of 5 in the v10 blog series. Details at the bottom.)*
10
-
11
- ### 1. The Problem: Prop-Drilling and the "Context Tax"
12
-
13
- Every application developer knows the pain. You have a piece of state—a user object, a theme setting—that needs to be
14
- accessed by a component buried deep within your UI tree. The traditional approach is "prop-drilling": passing that data
15
- down through every single intermediate component. It's tedious, error-prone, and creates a tight coupling between
16
- components that shouldn't know about each other.
17
-
18
- Modern frameworks solve this with a "Context API," a central provider that makes state available to any descendant.
19
- While this solves prop-drilling, it often introduces a hidden performance penalty: the "Context Tax." In many
20
- implementations, when *any* value in the context changes, *all* components consuming that context are forced to re-render,
21
- even if they don't care about the specific piece of data that changed. This can lead to significant,
22
- unnecessary rendering work.
7
+ This is the story of how we built a state provider from the ground up to solve this problem, delivering the convenience of a context API without the performance tax.
23
8
 
24
- Neo.mjs v10's `state.Provider` is designed to give you the convenience of a context API without this performance tax.
9
+ *(Part 5 of 5 in the v10 blog series. Details at the bottom.)*
25
10
 
26
- ### 2. The Neo.mjs Solution: From Custom Parsing to a Universal Foundation
11
+ ### The Evolution: From Custom Parsing to a Universal Foundation
27
12
 
28
- Neo.mjs has had a state provider for a long time, and it was already reactive. So, what’s the big deal with the v10 version?
29
- The difference lies in the *foundation*.
13
+ Neo.mjs has had a state provider for a long time, and it was already reactive. So, what’s the big deal with the v10 version? The difference lies in the *foundation*.
30
14
 
31
- The previous state provider was a clever, custom-built system. It worked by parsing your binding functions with regular
32
- expressions to figure out which `data` properties you were using. This was effective, but had two major limitations:
15
+ The previous state provider was a clever, custom-built system. It worked by parsing your binding functions with regular expressions to figure out which `data` properties you were using. This was effective, but had two major limitations:
33
16
 
34
- 1. **It Only Worked for `data`:** You could only bind to properties inside the provider's `data` object. Binding to an
35
- external store's `count` or another component's `width` was simply not possible.
36
- 2. **It Was Brittle:** Relying on regex parsing meant that complex or unconventionally formatted binding functions could
37
- sometimes fail to register dependencies correctly, leading to frustrating debugging sessions.
17
+ 1. **It Only Worked for `data`:** You could only bind to properties inside the provider's `data` object. Binding to an external store's `count` or another component's `width` was simply not possible.
18
+ 2. **It Was Brittle:** Relying on regex parsing meant that complex or unconventionally formatted binding functions could sometimes fail to register dependencies correctly, leading to frustrating debugging sessions.
38
19
 
39
- The v10 revolution was to throw out this custom parsing logic and rebuild the entire state management system on top of a
40
- universal, foundational concept: **`Neo.core.Effect`**.
20
+ The v10 revolution was to throw out this custom parsing logic and rebuild the entire state management system on top of a universal, foundational concept: **`Neo.core.Effect`**.
41
21
 
42
- This new foundation is what makes the modern `state.Provider` so powerful. It doesn't need to guess your dependencies;
43
- it *knows* them. When a binding function runs, `core.Effect` observes every reactive property you access—no matter where
44
- it lives—and builds a precise dependency graph in real-time.
22
+ This new foundation is what makes the modern `state.Provider` so powerful. It doesn't need to guess your dependencies; it *knows* them. When a binding function runs, `core.Effect` observes every reactive property you access—no matter where it lives—and builds a precise dependency graph in real-time.
45
23
 
46
- The result is an API that is not only more powerful but also simpler and more intuitive, especially when it comes to
47
- changing state. The provider does what you would expect, automatically handling complex scenarios like deep merging.
24
+ The result is an API that is not only more powerful but also simpler and more intuitive, especially when it comes to changing state. The provider does what you would expect, automatically handling complex scenarios like deep merging.
48
25
 
49
- Where the magic truly begins is in how you *change* that data. Thanks to the new deep, proxy-based reactivity system,
50
- you can modify state with plain JavaScript assignments. It's as simple as it gets:
26
+ Where the magic truly begins is in how you *change* that data. Thanks to the new deep, proxy-based reactivity system, you can modify state with plain JavaScript assignments. It's as simple as it gets:
51
27
 
52
- ```javascript readonly
28
+ ```javascript
53
29
  // Get the provider and change the data directly
54
30
  const provider = myComponent.getStateProvider();
55
31
 
@@ -118,14 +94,11 @@ class MainView extends Container {
118
94
  }
119
95
  MainView = Neo.setupClass(MainView);
120
96
  ```
121
- Notice the "Change First Name" button. It calls `setState` with an object that only contains `firstName`. The v10 provider
122
- is smart enough to perform a deep merge, updating `firstName` while leaving `lastName` untouched. This prevents accidental
123
- data loss and makes state updates safe and predictable by default.
97
+ Notice the "Change First Name" button. It calls `setState` with an object that only contains `firstName`. The v10 provider is smart enough to perform a deep merge, updating `firstName` while leaving `lastName` untouched. This prevents accidental data loss and makes state updates safe and predictable by default.
124
98
 
125
- ### 3. The Power of Formulas: Derived State Made Easy
99
+ ### The Power of Formulas: Derived State Made Easy
126
100
 
127
- Because the provider is built on `Neo.core.Effect`, creating computed properties ("formulas") is a native, first-class
128
- feature. You define them in a separate `formulas` config, and the provider automatically keeps them updated.
101
+ Because the provider is built on `Neo.core.Effect`, creating computed properties ("formulas") is a native, first-class feature. You define them in a separate `formulas` config, and the provider automatically keeps them updated.
129
102
 
130
103
  ```javascript live-preview
131
104
  import Container from 'neo.mjs/src/container/Base.mjs';
@@ -170,13 +143,11 @@ class MainView extends Container {
170
143
  }
171
144
  MainView = Neo.setupClass(MainView);
172
145
  ```
173
- When you edit the text fields, the `setState` call updates the base `user` data. The `Effect` system detects this,
174
- automatically re-runs the `fullName` formula, and updates the welcome label.
146
+ When you edit the text fields, the `setState` call updates the base `user` data. The `Effect` system detects this, automatically re-runs the `fullName` formula, and updates the welcome label.
175
147
 
176
- ### 4. Formulas Across Hierarchies
148
+ ### Formulas Across Hierarchies
177
149
 
178
- The true power of the hierarchical system is revealed when formulas in a child provider can seamlessly use data from a
179
- parent. This allows you to create powerful, scoped calculations that still react to global application state.
150
+ The true power of the hierarchical system is revealed when formulas in a child provider can seamlessly use data from a parent. This allows you to create powerful, scoped calculations that still react to global application state.
180
151
 
181
152
  ```javascript live-preview
182
153
  import Button from 'neo.mjs/src/button/Base.mjs';
@@ -237,14 +208,11 @@ class MainView extends Container {
237
208
  }
238
209
  MainView = Neo.setupClass(MainView);
239
210
  ```
240
- In this example, the child provider's `totalPrice` formula depends on its own local `price` and the parent's `taxRate`.
241
- Clicking either button triggers the correct reactive update, and the total price is always in sync. This demonstrates
242
- the effortless composition of state across different parts of your application.
211
+ In this example, the child provider's `totalPrice` formula depends on its own local `price` and the parent's `taxRate`. Clicking either button triggers the correct reactive update, and the total price is always in sync. This demonstrates the effortless composition of state across different parts of your application.
243
212
 
244
- ### 5. Hierarchical by Design: Nested Providers That Just Work
213
+ ### Hierarchical by Design: Nested Providers That Just Work
245
214
 
246
- The v10 provider was engineered to handle different scopes of state with an intelligent hierarchical model. A child
247
- component can seamlessly access data from its own provider as well as any parent provider.
215
+ The v10 provider was engineered to handle different scopes of state with an intelligent hierarchical model. A child component can seamlessly access data from its own provider as well as any parent provider.
248
216
 
249
217
  ```javascript live-preview
250
218
  import Container from 'neo.mjs/src/container/Base.mjs';
@@ -277,13 +245,11 @@ class MainView extends Container {
277
245
  }
278
246
  MainView = Neo.setupClass(MainView);
279
247
  ```
280
- The nested component can access both `user` from its local provider and `theme` from the parent provider without any
281
- extra configuration.
248
+ The nested component can access both `user` from its local provider and `theme` from the parent provider without any extra configuration.
282
249
 
283
- ### 5. The Final Piece: State Providers in Functional Components
250
+ ### State Providers in Functional Components
284
251
 
285
- Thanks to the v10 refactoring, state providers are now a first-class citizen in functional components. You can define a
286
- provider and bind to its data with the same power and simplicity as in class-based components.
252
+ Thanks to the v10 refactoring, state providers are now a first-class citizen in functional components. You can define a provider and bind to its data with the same power and simplicity as in class-based components.
287
253
 
288
254
  ```javascript live-preview
289
255
  import {defineComponent} from 'neo.mjs';
@@ -331,82 +297,21 @@ export default defineComponent({
331
297
  This example demonstrates the full power of the new architecture: a functional component with its own reactive data,
332
298
  computed properties, and two-way bindings, all with clean, declarative code.
333
299
 
334
- ### 6. From Theory to Practice: The Comprehensive Guide
335
-
336
- The examples above show the clean, intuitive API. For a complete, hands-on exploration with dozens of live-preview
337
- examples covering everything from nested providers and formulas to advanced store management, we encourage you to
338
- explore our comprehensive guide. The rest of this article will focus on the deep architectural advantages that make
339
- this system possible.
340
-
341
- **[Read the Full State Providers Guide Here](../guides/datahandling/StateProviders.md)**
342
-
343
- ### 7. Under the Hood Part 1: The Proxy's Magic
344
-
345
- The beautiful API above is powered by a sophisticated proxy created by `Neo.state.createHierarchicalDataProxy`.
346
- When you interact with `provider.data`, you're not touching a plain object; you're interacting with an intelligent agent
347
- that works with Neo's `EffectManager`.
348
-
349
- You can see the full implementation in
350
- **[src/state/createHierarchicalDataProxy.mjs](../../src/state/createHierarchicalDataProxy.mjs)**.
351
-
352
- Here’s how it works:
353
-
354
- 1. **The `get` Trap:** When your binding function (`data => data.user.firstname`) runs for the first time, it accesses
355
- properties on the proxy. The proxy's `get` trap intercepts these reads and tells the `EffectManager`,
356
- "The currently running effect depends on the `user.firstname` config." This builds a dependency graph automatically.
357
- 2. **The `set` Trap:** When you write `provider.data.user.firstname = 'Max'`, the proxy's `set` trap intercepts the
358
- assignment. It then calls the provider's internal `setData('user.firstname', 'Max')` method, which triggers the
359
- reactivity system to re-run only the effects that depend on that specific property.
360
-
361
- This proxy is the bridge between a simple developer experience and a powerful, fine-grained reactive engine.
300
+ ### Under the Hood: The Proxy and "Reactivity Bubbling"
362
301
 
363
- ### 8. Under the Hood Part 2: The "Reactivity Bubbling" Killer Feature
302
+ The beautiful API above is powered by a sophisticated proxy created by `Neo.state.createHierarchicalDataProxy`. When you
303
+ interact with `provider.data`, you're not touching a plain object; you're interacting with an intelligent agent that
304
+ works with Neo's `EffectManager`.
364
305
 
365
- This is where the Neo.mjs `state.Provider` truly shines and solves the "Context Tax." Consider this critical question:
306
+ 1. **The `get` Trap:** When your binding function runs, the proxy's `get` trap intercepts these reads and tells the
307
+ `EffectManager`, "The currently running effect depends on this property." This builds a dependency graph automatically.
308
+ 2. **The `set` Trap:** When you write `provider.data.user.firstname = 'Max'`, the proxy's `set` trap intercepts the assignment.
309
+ It then calls the provider's internal `setData()` method, which triggers the reactivity system to re-run only the
310
+ effects that depend on that specific property.
366
311
 
367
- > "What happens if a component is bound to the entire `data.user` object, and we only change `data.user.name`?"
368
-
369
- In many systems, this would not trigger an update, because the reference to the `user` object itself hasn't changed.
370
- This is a common "gotcha" that forces developers into complex workarounds.
371
-
372
- Neo.mjs handles this intuitively with a feature we call **"reactivity bubbling."** A change to a leaf property is
373
- correctly perceived as a change to its parent.
374
-
375
- We don't just claim this works; we prove it. Our test suite for this exact behavior,
376
- **[test/siesta/tests/state/ProviderNestedDataConfigs.mjs](../../test/siesta/tests/state/ProviderNestedDataConfigs.mjs)**,
377
- demonstrates this with concrete assertions.
378
-
379
- Here’s a simplified version of the test:
380
-
381
- ```javascript
382
- // From test/siesta/tests/state/ProviderNestedDataConfigs.mjs
383
- t.it('State Provider should trigger parent effects when a leaf node changes (bubbling)', t => {
384
- let effectRunCount = 0;
385
-
386
- const component = Neo.create(MockComponent, {
387
- stateProvider: { data: { user: { name: 'John', age: 30 } } }
388
- });
389
- const provider = component.getStateProvider();
390
-
391
- // This binding depends on the 'user' object itself.
392
- provider.createBinding(component.id, 'user', data => {
393
- effectRunCount++;
394
- return data.user;
395
- });
396
-
397
- t.is(effectRunCount, 1, 'Effect should run once initially');
398
-
399
- // Change a leaf property.
400
- provider.setData('user.age', 31);
401
-
402
- // Assert that the effect depending on the PARENT object re-ran.
403
- t.is(effectRunCount, 2, 'Effect should re-run after changing a leaf property');
404
- });
405
- ```
406
- This behavior is made possible by the `internalSetData` method in **[state/Provider.mjs](../../src/state/Provider.mjs)**.
407
- When you set `'user.age'`, the provider doesn't just update that one value. It then "bubbles up," creating a new `user`
408
- object reference that incorporates the change: `{...oldUser, age: 31}`. This new object reference is what the reactivity
409
- system detects, ensuring that any component bound to `user` updates correctly.
312
+ This proxy is the bridge between a simple developer experience and a powerful, fine-grained reactive engine. It also
313
+ enables a key feature we call **"reactivity bubbling."** A change to a leaf property (e.g., `user.name`) is correctly
314
+ perceived as a change to its parent (`user`), ensuring that components bound to the parent object update as expected.
410
315
 
411
316
  ### Conclusion: Reactivity at the Core
412
317
 
@@ -427,6 +332,6 @@ This is what a ground-up reactive system enables, and it's a cornerstone of the
427
332
 
428
333
  1. [A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow](./v10-post1-love-story.md)
429
334
  2. [Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity](./v10-deep-dive-reactivity.md)
430
- 3. [Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
431
- 4. [Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering](./v10-deep-dive-vdom-revolution.md)
432
- 5. Deep Dive: The State Provider Revolution
335
+ 3. [Designing Functional Components for a Multi-Threaded World](./v10-deep-dive-functional-components.md)
336
+ 4. [The VDOM Revolution: How We Render UIs from a Web Worker](./v10-deep-dive-vdom-revolution.md)
337
+ 5. Designing a State Manager for Performance: A Deep Dive into Hierarchical Reactivity