@ape-egg/vibe 1.0.2 → 1.0.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.5] - 2026-01-25
4
+
5
+ ### Fixed
6
+
7
+ - **Expression evaluation**: Normalize whitespace in `@[...]` expressions before evaluation, making bindings resilient to IDE auto-formatting that may break expressions across multiple lines
8
+
9
+ ### Changed
10
+
11
+ - **DRY refactor**: Consolidated expression evaluation into single `evalInScope()` function in utils.js, used by hydrate.js, conditionals.js, and affected.js
12
+
13
+ ---
14
+
15
+ ## [1.0.4] - 2026-01-24
16
+
17
+ ### Fixed
18
+
19
+ - **MutationObserver**: Use `takeRecords()` to preserve pending mutations before disconnecting during state changes, preventing queued DOM mutations (e.g., innerHTML replacements) from being lost when state updates occur simultaneously
20
+
21
+ ---
22
+
23
+ ## [1.0.3] - 2026-01-23
24
+
25
+ ### Added
26
+
27
+ - **MutationObserver**: Now processes `<!-- if -->` and `<!-- each -->` comments when new nodes are added to DOM, enabling dynamic content to use vibe's control flow without manual processing
28
+
29
+ ### Fixed
30
+
31
+ - **affected.js**: Only marks elements as affected when their specific bound values change, preventing unnecessary DOM updates when unrelated state changes (fixes `@[color]` in `<style>` updating on every keystroke when `@[firstName]` changes)
32
+
33
+ ---
34
+
3
35
  ## [1.0.2] - 2026-01-20
4
36
 
5
37
  ### Fixed
package/ROADMAP.md ADDED
@@ -0,0 +1,289 @@
1
+ # Vibe Roadmap
2
+
3
+ Feature proposals and improvements for Vibe's runtime-first reactive framework.
4
+
5
+ ---
6
+
7
+ ## Proposed: Manual Hydration API
8
+
9
+ **Status**: Proposal
10
+ **Priority**: Medium
11
+ **Category**: Core Runtime
12
+
13
+ ### Problem
14
+
15
+ Vibe's MutationObserver automatically hydrates `@[bindings]` on initial page load and incremental DOM changes, but fails when developers perform wholesale DOM replacement via `innerHTML`:
16
+
17
+ ```js
18
+ // This doesn't trigger Vibe's hydration:
19
+ element.innerHTML = '<h1>Welcome, @[race]!</h1>';
20
+ // Result: Literal text "@[race]" instead of evaluated "human"
21
+ ```
22
+
23
+ This happens because:
24
+ 1. `innerHTML` replacement destroys all old DOM nodes and creates new ones
25
+ 2. Vibe's MutationObserver is designed for incremental mutations, not complete replacement
26
+ 3. No mechanism exists to manually trigger re-hydration
27
+
28
+ ### Current Workaround
29
+
30
+ Users must manually evaluate bindings before setting innerHTML:
31
+
32
+ ```js
33
+ const evaluateBindings = (html) => {
34
+ return html.replace(/@\[([^\]]+)\]/g, (match, expression) => {
35
+ const keys = Object.keys(window.$);
36
+ const values = Object.values(window.$);
37
+ const result = new Function(...keys, `return ${expression}`)(...values);
38
+ return result ?? '';
39
+ });
40
+ };
41
+
42
+ element.innerHTML = evaluateBindings(html); // Manually evaluated
43
+ ```
44
+
45
+ **Issues with this approach:**
46
+ - Not DRY - duplicates Vibe's internal evaluation logic
47
+ - Fragile - user's regex might not match Vibe's parser exactly
48
+ - Knowledge burden - users need to know when manual evaluation is needed
49
+ - Inconsistent - some bindings auto-hydrate, others need manual work
50
+
51
+ ### Use Cases
52
+
53
+ This affects multiple real-world scenarios:
54
+
55
+ 1. **Dynamic content replacement** (tutorials, articles, modals)
56
+ 2. **Client-side routing** (replacing page sections with new HTML)
57
+ 3. **Lazy-loaded sections** (loading HTML from server with bindings)
58
+ 4. **Template cloning** (using `<template>` elements with `@[bindings]`)
59
+ 5. **Server-sent HTML** (SSR-like patterns where server sends HTML with bindings)
60
+
61
+ ### Proposed Solution
62
+
63
+ Add a manual hydration API that allows users to trigger Vibe's binding evaluation:
64
+
65
+ #### Option 1: Element Hydration
66
+ ```js
67
+ vibe.hydrate(element);
68
+ ```
69
+
70
+ **Usage:**
71
+ ```js
72
+ tutorial.innerHTML = '<h1>Welcome, @[race]!</h1>';
73
+ vibe.hydrate(tutorial); // Scan tutorial and children for @[bindings]
74
+ ```
75
+
76
+ **Pros:**
77
+ - Most flexible - works with any element
78
+ - Matches web component patterns (`connectedCallback()`)
79
+ - Clear intent - "scan this element"
80
+
81
+ **Cons:**
82
+ - Requires import/reference to vibe library
83
+ - Two-step process (set innerHTML, then hydrate)
84
+
85
+ #### Option 2: HTML String Evaluation
86
+ ```js
87
+ const evaluated = vibe.evaluate(html, state);
88
+ ```
89
+
90
+ **Usage:**
91
+ ```js
92
+ const html = '<h1>Welcome, @[race]!</h1>';
93
+ const evaluated = vibe.evaluate(html, window.$);
94
+ tutorial.innerHTML = evaluated;
95
+ ```
96
+
97
+ **Pros:**
98
+ - Pure function - easier to test
99
+ - Works without DOM access
100
+ - Can be used server-side or in workers
101
+
102
+ **Cons:**
103
+ - Users must manage state passing
104
+ - Doesn't handle nested/dynamic state updates
105
+
106
+ #### Option 3: Safe innerHTML Setter
107
+ ```js
108
+ vibe.setHTML(element, html);
109
+ ```
110
+
111
+ **Usage:**
112
+ ```js
113
+ vibe.setHTML(tutorial, '<h1>Welcome, @[race]!</h1>');
114
+ ```
115
+
116
+ **Pros:**
117
+ - Single operation - set and hydrate in one call
118
+ - Matches platform APIs (`element.setHTML()`)
119
+ - Simplest API surface
120
+
121
+ **Cons:**
122
+ - Yet another setter abstraction
123
+ - Might conflict with future platform APIs
124
+
125
+ ### Recommendation
126
+
127
+ **Implement Option 1** (`vibe.hydrate(element)`):
128
+ - Aligns with Vibe's runtime-first philosophy
129
+ - Gives users explicit control over hydration timing
130
+ - Most flexible for different scenarios
131
+ - Clear and predictable behavior
132
+
133
+ ### Implementation Notes
134
+
135
+ ```js
136
+ // Expose on the state proxy:
137
+ window.$ = state({ race: 'human' });
138
+ window.$.vibe.hydrate(element); // Scan element for @[bindings]
139
+
140
+ // Or as a module export:
141
+ import state, { hydrate } from '@ape-egg/vibe';
142
+ hydrate(element);
143
+ ```
144
+
145
+ Should support:
146
+ - Single element: `hydrate(tutorial)`
147
+ - Multiple elements: `hydrate([el1, el2])`
148
+ - Selector: `hydrate('tutorial')` (convenience)
149
+
150
+ ### Related
151
+
152
+ Compare to other frameworks:
153
+ - **Alpine.js**: `Alpine.initTree(el)` - manual initialization
154
+ - **Vue**: `app.mount(el)` - mount to element
155
+ - **Svelte**: Compiler handles this at build time
156
+ - **HTMX**: `htmx.process(el)` - process element for attributes
157
+
158
+ ---
159
+
160
+ ## Known Issues
161
+
162
+ ### innerHTML Replacement Corrupts Conditional Branches in Iterations
163
+
164
+ **Status**: Bug
165
+ **Priority**: Low (edge case)
166
+ **Category**: Core Runtime
167
+ **Discovered**: 2026-01-25
168
+
169
+ #### Problem
170
+
171
+ When a parent element containing iterations with nested conditionals has its `innerHTML` replaced multiple times with identical HTML, the conditional's `else` branches become `null` after 2-3 replacements. This causes conditionals to fail rendering.
172
+
173
+ **Root cause:**
174
+ 1. Iteration template's `branches` object is shared across all iteration instances (iterate.js:65: `branches, // Branch templates are reused`)
175
+ 2. When `innerHTML` replacement happens, something mutates the shared `branches.else` to `null`
176
+ 3. All instances reference the same corrupted branches object
177
+ 4. Subsequent renders have no `else` branch template to mount
178
+
179
+ #### Reproduction
180
+
181
+ Programmatic test case:
182
+
183
+ ```js
184
+ // HTML structure
185
+ const html = `
186
+ <item-list>
187
+ <!-- each items as item, i -->
188
+ <item-card>
189
+ <span>@[item]</span>
190
+ <!-- if i % 2 === 0 -->
191
+ <badge>Even</badge>
192
+ <!-- else -->
193
+ <badge secondary>Odd</badge>
194
+ <!-- /if -->
195
+ </item-card>
196
+ <!-- /each -->
197
+ </item-list>
198
+ `;
199
+
200
+ // State
201
+ window.$ = state({ items: ['Apple', 'Banana', 'Cherry'] });
202
+
203
+ // Trigger the bug
204
+ const container = document.querySelector('[vibe]');
205
+
206
+ // First replacement: works
207
+ container.innerHTML = html;
208
+ await new Promise(r => setTimeout(r, 100));
209
+
210
+ // Second replacement: works
211
+ container.innerHTML = html;
212
+ await new Promise(r => setTimeout(r, 100));
213
+
214
+ // Third replacement: branches.else becomes NULL
215
+ container.innerHTML = html;
216
+ await new Promise(r => setTimeout(r, 100));
217
+
218
+ // Result: Even/Odd badges fail to render in the third iteration
219
+ ```
220
+
221
+ #### Observations
222
+
223
+ 1. Cloning `branches` object during iteration (shallow copy) doesn't prevent the bug
224
+ 2. The mutation happens BEFORE cloning, meaning the original template is corrupted
225
+ 3. Setting a property trap on `branches.else` doesn't catch the mutation (already null when accessed)
226
+ 4. Only affects conditionals inside iterations - standalone conditionals work fine
227
+ 5. Only triggers with multiple innerHTML replacements - single replacement works
228
+
229
+ #### Affected Patterns
230
+
231
+ This bug only affects:
232
+ - Replacing innerHTML multiple times with identical HTML containing iterations + conditionals
233
+ - Demo infrastructure like tutorial.js that re-renders on state changes
234
+ - Not representative of typical usage patterns
235
+
236
+ Does NOT affect:
237
+ - Normal reactivity (state changes)
238
+ - Single innerHTML replacement
239
+ - Iterations without conditionals
240
+ - Conditionals outside iterations
241
+ - Incremental DOM mutations (appendChild, insertBefore, etc.)
242
+
243
+ #### Workaround
244
+
245
+ Avoid multiple innerHTML replacements on parents containing iteration+conditional templates. Instead:
246
+ 1. Use incremental DOM APIs (appendChild, createElement)
247
+ 2. Replace innerHTML once at initialization only
248
+ 3. Use Vibe's normal reactivity for updates
249
+ 4. Don't wrap demo content in `<tutorial>` that re-renders via innerHTML
250
+
251
+ #### Investigation Log
252
+
253
+ Debugging attempts (2026-01-25):
254
+ - ✓ Confirmed `branches.else` becomes `null` after 3rd innerHTML replacement
255
+ - ✓ Added deep cloning of branches object - didn't help (already null before clone)
256
+ - ✓ Added Object.defineProperty trap - didn't fire (already null)
257
+ - ✓ Checked parsing logic - correctly finds else comments
258
+ - ✗ Unable to identify where mutation occurs
259
+ - ✗ Unable to reproduce with simpler test case (needs tutorial.js pattern)
260
+
261
+ Likely related to:
262
+ - MutationObserver's removedNodes callback cleaning up references
263
+ - Template caching/reuse strategy in iterate.js
264
+ - Interaction between parse → clone → hydrate → render cycle
265
+
266
+ #### Resolution Path
267
+
268
+ **Phase 1 (Current)**: Document and work around
269
+ - Remove `<tutorial>` wrapper from demos
270
+ - Add note in CLAUDE.md about limitation
271
+ - Tests validate core reactivity works correctly
272
+
273
+ **Phase 2+**: Consider fixing if real-world need emerges
274
+ - Deep investigation into branch reference lifecycle
275
+ - Possibly: deep clone branches instead of sharing reference
276
+ - Possibly: rebuild conditional metadata on each innerHTML replacement
277
+ - Possibly: manual hydration API (see "Manual Hydration API" proposal above)
278
+
279
+ This is acceptable technical debt since:
280
+ 1. Edge case not representative of normal usage
281
+ 2. Core reactivity (the 99% case) works correctly
282
+ 3. Can be addressed when/if users report needing this pattern
283
+ 4. Phase 1 goals (iteration + conditionals) are met
284
+
285
+ ---
286
+
287
+ ## Future Proposals
288
+
289
+ *This section reserved for additional feature proposals*
@@ -58,13 +58,13 @@ const hasNestedStructures = (tree) => {
58
58
  */
59
59
  export const compileBatchFn = (template, itemAlias, indexAlias, stateKeys) => {
60
60
  const templateHtml = template.element.innerHTML.trim();
61
- const escaped = templateHtml
62
- .replace(/\\/g, '\\\\')
63
- .replace(/`/g, '\\`')
64
- .replace(/\$\{/g, '\\${');
61
+ const escaped = templateHtml.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
65
62
  const code = escaped.replace(BINDING_REGEX, (_, expr) => '${' + expr + '}');
66
63
 
67
- return new Function('arr', ...stateKeys, `
64
+ return new Function(
65
+ 'arr',
66
+ ...stateKeys,
67
+ `
68
68
  let html = '';
69
69
  const len = arr.length;
70
70
  for (let ${indexAlias} = 0; ${indexAlias} < len; ${indexAlias}++) {
@@ -72,7 +72,8 @@ export const compileBatchFn = (template, itemAlias, indexAlias, stateKeys) => {
72
72
  html += \`${code}\`;
73
73
  }
74
74
  return html;
75
- `);
75
+ `,
76
+ );
76
77
  };
77
78
 
78
79
  /**
@@ -92,7 +93,7 @@ export const renderFast = (iterationNode, array, state, parent, endComment) => {
92
93
  const stateKeys = iterationNode.runtime.stateKeys;
93
94
 
94
95
  // Build HTML string using batch function
95
- const stateValues = stateKeys.map(k => state[k]);
96
+ const stateValues = stateKeys.map((k) => state[k]);
96
97
  const html = batchFn(array, ...stateValues);
97
98
 
98
99
  // Parse with reusable template element
@@ -141,7 +142,7 @@ export const updateFast = (iterationNode, newArray, state, startComment, endComm
141
142
  // Build HTML string using cached batch function
142
143
  const batchFn = iterationNode.runtime.batchFn;
143
144
  const stateKeys = iterationNode.runtime.stateKeys;
144
- const stateValues = stateKeys.map(k => state[k]);
145
+ const stateValues = stateKeys.map((k) => state[k]);
145
146
  const html = batchFn(newArray, ...stateValues);
146
147
 
147
148
  // Parse and insert
package/affected.js CHANGED
@@ -1,18 +1,10 @@
1
- import { resolvePath, deepEqual } from './iteration-utils.js'
2
- import { extractDependencies } from './conditionals.js'
3
- import { BINDING_REGEX } from './constants.js'
1
+ import { resolvePath, deepEqual } from './iteration-utils.js';
2
+ import { extractDependencies } from './conditionals.js';
3
+ import { BINDING_REGEX } from './constants.js';
4
+ import { evalInScope } from './utils.js';
4
5
 
5
6
  // Evaluate conditional expression
6
- const evaluateCondition = (expression, state) => {
7
- try {
8
- const keys = Object.keys(state);
9
- const values = Object.values(state);
10
- const result = new Function(...keys, `'use strict'; return !!(${expression})`)(...values);
11
- return !!result;
12
- } catch (e) {
13
- return false;
14
- }
15
- };
7
+ const evaluateCondition = (expression, state) => !!evalInScope(expression, state);
16
8
 
17
9
  // Helper function to check if a match references a specific key
18
10
  const matchesKey = (matchStr, key) => matchStr === key || matchStr.startsWith(key + '.');
@@ -29,7 +21,7 @@ const recursive = (tree, state, newState, affected) => {
29
21
  affected.push({
30
22
  type: 'iteration',
31
23
  node: tree,
32
- changeType: 'array'
24
+ changeType: 'array',
33
25
  });
34
26
  }
35
27
  return affected;
@@ -45,7 +37,7 @@ const recursive = (tree, state, newState, affected) => {
45
37
  affected.push({
46
38
  type: 'conditional',
47
39
  node: tree,
48
- changeType: 'expression'
40
+ changeType: 'expression',
49
41
  });
50
42
  return affected;
51
43
  }
@@ -70,21 +62,37 @@ const recursive = (tree, state, newState, affected) => {
70
62
  if (matches.length) {
71
63
  const shallowState = Object.keys(state);
72
64
  const shallowNewState = Object.keys(newState);
65
+ const isInitialHydration = state === newState;
73
66
 
74
67
  let hasAffected = false;
75
68
  const checkedMatches = [];
76
69
 
77
70
  for (const m of matches) {
78
- const noMatch = !shallowState.some(key => matchesKey(m.inner, key));
79
- const newMatches = shallowNewState.filter(key => matchesKey(m.inner, key));
71
+ const noMatch = !shallowState.some((key) => matchesKey(m.inner, key));
72
+
73
+ let shouldAffect = false;
74
+ let relevantKeys = [];
75
+
76
+ if (isInitialHydration) {
77
+ // Initial hydration: affect all matched keys
78
+ relevantKeys = shallowNewState.filter((key) => matchesKey(m.inner, key));
79
+ shouldAffect = noMatch || relevantKeys.length > 0;
80
+ } else {
81
+ // Update: only affect if value changed
82
+ const changedKeys = shallowNewState.filter((key) =>
83
+ matchesKey(m.inner, key) && state[key] !== newState[key]
84
+ );
85
+ relevantKeys = changedKeys.length > 0 ? changedKeys : shallowState.filter((key) => matchesKey(m.inner, key));
86
+ shouldAffect = noMatch || changedKeys.length > 0;
87
+ }
80
88
 
81
- if (noMatch || newMatches.length) {
89
+ if (shouldAffect) {
82
90
  hasAffected = true;
83
91
  }
84
92
 
85
93
  checkedMatches.push({
86
94
  ...m,
87
- matches: newMatches.length ? newMatches : shallowState.filter(key => matchesKey(m.inner, key))
95
+ matches: relevantKeys,
88
96
  });
89
97
  }
90
98
 
@@ -95,7 +103,7 @@ const recursive = (tree, state, newState, affected) => {
95
103
  matchInner: m.inner,
96
104
  input: m.input,
97
105
  matches: m.matches,
98
- element: tree.element
106
+ element: tree.element,
99
107
  });
100
108
  }
101
109
  }
@@ -105,6 +113,7 @@ const recursive = (tree, state, newState, affected) => {
105
113
  if (tree.attributes) {
106
114
  const shallowState = Object.keys(state);
107
115
  const shallowNewState = Object.keys(newState);
116
+ const isInitialHydration = state === newState;
108
117
 
109
118
  for (const [attrName, attrValue] of Object.entries(tree.attributes)) {
110
119
  BINDING_REGEX.lastIndex = 0;
@@ -115,17 +124,30 @@ const recursive = (tree, state, newState, affected) => {
115
124
  }
116
125
 
117
126
  for (const m of attrMatches) {
118
- const noMatch = !shallowState.some(key => matchesKey(m.inner, key));
119
- const newMatches = shallowNewState.filter(key => matchesKey(m.inner, key));
127
+ const noMatch = !shallowState.some((key) => matchesKey(m.inner, key));
128
+
129
+ let shouldAffect = false;
130
+
131
+ if (isInitialHydration) {
132
+ // Initial hydration: affect all matched keys
133
+ const newMatches = shallowNewState.filter((key) => matchesKey(m.inner, key));
134
+ shouldAffect = noMatch || newMatches.length > 0;
135
+ } else {
136
+ // Update: only affect if value changed
137
+ const changedKeys = shallowNewState.filter((key) =>
138
+ matchesKey(m.inner, key) && state[key] !== newState[key]
139
+ );
140
+ shouldAffect = noMatch || changedKeys.length > 0;
141
+ }
120
142
 
121
- if (noMatch || newMatches.length) {
143
+ if (shouldAffect) {
122
144
  affected.push({
123
145
  type: 'attribute',
124
146
  attrName,
125
147
  attrValue,
126
148
  matchOuter: m.outer,
127
149
  matchInner: m.inner,
128
- element: tree.element
150
+ element: tree.element,
129
151
  });
130
152
  }
131
153
  }
@@ -146,4 +168,4 @@ const recursive = (tree, state, newState, affected) => {
146
168
  return affected;
147
169
  };
148
170
 
149
- export default (tree, state, newState) => recursive(tree, state, newState, [])
171
+ export default (tree, state, newState) => recursive(tree, state, newState, []);
package/conditionals.js CHANGED
@@ -2,20 +2,10 @@ import parse from './parse.js';
2
2
  import affected from './affected.js';
3
3
  import hydrate from './hydrate.js';
4
4
  import { createScopedState, renderAllIterations, initializeBlock } from './iterate.js';
5
+ import { evalInScope } from './utils.js';
5
6
 
6
7
  // Evaluate conditional expression in state context
7
- const evaluateCondition = (expression, state) => {
8
- try {
9
- const keys = Object.keys(state);
10
- const values = Object.values(state);
11
- // Create a function with state keys as parameters and evaluate the expression
12
- const result = new Function(...keys, `'use strict'; return !!(${expression})`)(...values);
13
- return !!result; // Coerce to boolean
14
- } catch (e) {
15
- console.warn(`Error evaluating condition "${expression}":`, e);
16
- return false; // Default to false on error
17
- }
18
- };
8
+ const evaluateCondition = (expression, state) => !!evalInScope(expression, state);
19
9
 
20
10
  // Extract state dependencies from an expression
21
11
  // e.g., "count > 5" → ["count"]
@@ -44,7 +34,7 @@ export const renderAllConditionals = (tree, state, linkList, parentScope = {}) =
44
34
 
45
35
  // Recursively render conditionals in child nodes
46
36
  if (tree.children) {
47
- Object.keys(tree.children).forEach(key => {
37
+ Object.keys(tree.children).forEach((key) => {
48
38
  const child = tree.children[key];
49
39
  if (typeof child === 'object' && child !== null) {
50
40
  renderAllConditionals(child, state, linkList, parentScope);
@@ -104,15 +94,18 @@ const mountBranch = (node, branchData, state, linkList, parentScope) => {
104
94
  const parent = startComment.parentNode;
105
95
 
106
96
  // Create scoped state (with parent scope if inside iteration)
107
- const scopedState = Object.keys(parentScope).length > 0
108
- ? createScopedState(state, parentScope)
109
- : state;
97
+ const scopedState =
98
+ Object.keys(parentScope).length > 0 ? createScopedState(state, parentScope) : state;
110
99
 
111
100
  // Initialize block (clone, parse, hydrate)
112
- const { element: firstElement, tree: branchTree, clonedNodes } = initializeBlock(templateContent, scopedState);
101
+ const {
102
+ element: firstElement,
103
+ tree: branchTree,
104
+ clonedNodes,
105
+ } = initializeBlock(templateContent, scopedState);
113
106
 
114
107
  // Insert cloned nodes into DOM
115
- clonedNodes.forEach(clonedNode => parent.insertBefore(clonedNode, endComment));
108
+ clonedNodes.forEach((clonedNode) => parent.insertBefore(clonedNode, endComment));
116
109
 
117
110
  // Recursively render any nested iterations and conditionals
118
111
  if (branchTree) {
@@ -124,7 +117,7 @@ const mountBranch = (node, branchData, state, linkList, parentScope) => {
124
117
  node.runtime.activeInstance = {
125
118
  branch: branchData,
126
119
  nodes: clonedNodes,
127
- parsedTree: branchTree
120
+ parsedTree: branchTree,
128
121
  };
129
122
  };
130
123
 
@@ -135,7 +128,7 @@ const unmountBranch = (node) => {
135
128
  if (!activeInstance) return;
136
129
 
137
130
  // Remove all nodes from DOM
138
- activeInstance.nodes.forEach(domNode => {
131
+ activeInstance.nodes.forEach((domNode) => {
139
132
  if (domNode.parentNode) {
140
133
  domNode.parentNode.removeChild(domNode);
141
134
  }
@@ -170,13 +163,11 @@ export const updateConditional = (node, newState, oldState, linkList, parentScop
170
163
  const { activeInstance } = node.runtime;
171
164
 
172
165
  if (activeInstance && activeInstance.parsedTree) {
173
- const scopedState = Object.keys(parentScope).length > 0
174
- ? createScopedState(newState, parentScope)
175
- : newState;
166
+ const scopedState =
167
+ Object.keys(parentScope).length > 0 ? createScopedState(newState, parentScope) : newState;
176
168
 
177
169
  const affectedElements = affected(activeInstance.parsedTree, oldState, scopedState);
178
170
  hydrate(affectedElements, scopedState);
179
171
  }
180
172
  }
181
173
  };
182
-