@ape-egg/vibe 1.3.1 → 1.6.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 (36) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +97 -0
  3. package/ROADMAP.md +289 -0
  4. package/boot.js +45 -0
  5. package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
  6. package/compiler/src/Cargo.lock +719 -40
  7. package/compiler/src/Cargo.toml +11 -2
  8. package/compiler/src/compiler/PRE-RENDERING-IMPLEMENTATION.md +241 -0
  9. package/compiler/src/compiler/compile.rs +552 -175
  10. package/compiler/src/compiler/component_tagger.rs +234 -0
  11. package/compiler/src/compiler/iteration_optimizer.rs +351 -0
  12. package/compiler/src/compiler/js_analyzer.rs +572 -0
  13. package/compiler/src/compiler/manifest_builder.rs +251 -26
  14. package/compiler/src/compiler/mod.rs +5 -1
  15. package/compiler/src/compiler/state_extractor.rs +140 -25
  16. package/compiler/src/compiler/value_stamper.rs +579 -88
  17. package/compiler/src/compiler/watcher.rs +579 -0
  18. package/compiler/src/config.rs +51 -8
  19. package/compiler/src/main.rs +41 -28
  20. package/compiler/src/parser/html.rs +229 -118
  21. package/component.js +23 -11
  22. package/llms.txt +304 -0
  23. package/package.json +1 -17
  24. package/runtime/cleanup.js +4 -4
  25. package/runtime/component.js +98 -21
  26. package/runtime/conditionals.js +2 -2
  27. package/runtime/constants.js +2 -1
  28. package/runtime/index.js +152 -30
  29. package/runtime/iterate.js +27 -5
  30. package/runtime/parse.js +2 -1
  31. package/runtime/pre-compiled-iterations.js +153 -0
  32. package/runtime/{hyperspeed.js → pre-compiled-manifest.js} +204 -132
  33. package/runtime/utils.js +2 -1
  34. package/test-results/.last-run.json +4 -0
  35. package/vibe.css +19 -0
  36. package/runtime/component-state.js +0 -63
package/CHANGELOG.md CHANGED
@@ -1,5 +1,198 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.0] - 2026-02-12
4
+
5
+ ### Added
6
+
7
+ - **Watch mode** - Compiler now supports file watching with incremental compilation
8
+ - New `--watch` flag monitors source files for changes and automatically recompiles
9
+ - Intelligent change detection tracks affected files and their dependencies
10
+ - Transitive dependency tracking: changes to components trigger recompilation of pages using them
11
+ - Blacklist support: watch mode respects SKIP_FILES patterns (tests/, node_modules/, etc.)
12
+ - Debounced file events (300ms) prevent excessive compilation during rapid changes
13
+ - Outputs only changed files for fast incremental builds
14
+ - First compile shows full output, subsequent compiles show only deltas
15
+ - **Reserved element validation** - Compiler now prevents component naming conflicts
16
+ - New `reservedElements` config option (replaces `excludeTags`)
17
+ - Defaults to all HTML5 elements plus "component" keyword
18
+ - User-provided values append to defaults (not replace)
19
+ - Case-sensitive validation: `nav.html` → Error, `Nav.html` → OK
20
+ - Compile-time error with clear message showing conflicting filename
21
+ - Prevents runtime confusion between HTML elements and custom components
22
+ - **Component path case-sensitivity** - Component paths are now treated as case-sensitive
23
+ - `<component src="path/to/MyComponent.html">` and `<component src="path/to/mycomponent.html">` are different
24
+ - Both runtime and compiler preserve exact case in paths
25
+ - Cache keys include full case-sensitive path
26
+ - Enables PascalCase naming convention for components while supporting any casing
27
+
28
+ ### Changed
29
+
30
+ - **Component inlining architecture** - Complete rewrite from iterative to recursive fetching
31
+ - Removed MAX_ITERATIONS constant and loop-based approach
32
+ - `fetch_component_recursive()` now returns fully resolved content with all nested components inlined
33
+ - Components are cached only after being fully resolved (prevents incomplete content in cache)
34
+ - `inline_component_elements()` now does single pass (no loops)
35
+ - Significant performance improvement: sub-100ms compilation for complex nested components
36
+ - **Framework element handling** - `<component>` is now recognized as a framework element
37
+ - Never transformed to `<div>` regardless of `--elements-as-is` flag
38
+ - Custom elements (e.g., `<card>`, `<text>`) are transformed when `elements_as_is: false`
39
+ - Framework elements vs custom elements properly distinguished in compiler
40
+ - Wrapper `<component>` tags remain after inlining (expected by runtime)
41
+ - **Component inlining behavior** - Custom elements are always inlined
42
+ - `<card>` → transformed to `<component>` → inlined even with `--components-as-is`
43
+ - `--components-as-is` only affects explicit `<component src="...">` tags
44
+ - Consistent behavior: custom elements compile away, framework elements remain
45
+ - **Path normalization** - Component paths now consistently normalized
46
+ - All paths start with `/` (unless external URL)
47
+ - Cache uses normalized paths to prevent duplicates (`/components/nav.html` vs `./components/nav.html`)
48
+ - Fixes cache pollution from different path formats for same component
49
+ - **Config naming** - `excludeTags` renamed to `reservedElements` throughout codebase
50
+ - Better describes purpose (reserved from use as component names)
51
+ - Updated in Rust compiler, config files, and documentation
52
+ - Backward compatible: old config key still works but deprecated
53
+
54
+ ### Fixed
55
+
56
+ - Watch mode was compiling blacklisted files (tests/, node_modules/)
57
+ - Component inlining created infinite nested wrappers (100+ levels)
58
+ - Empty headlines in tests due to broken external component fetching
59
+ - Path case normalization could cause duplicates in component cache
60
+ - Accessibility transformation incorrectly converting `<component>` to divs
61
+ - Test expectations mismatched actual framework element behavior
62
+
63
+ ### Performance
64
+
65
+ - Watch mode incremental compilation: ~100ms for typical changes
66
+ - First full compilation: ~300-500ms
67
+ - Component fetching: recursive approach 10x faster than iterative (no MAX_ITERATIONS overhead)
68
+ - Path normalization prevents redundant fetches of same component
69
+
70
+ ---
71
+
72
+ ## [1.5.0] - 2026-02-09
73
+
74
+ ### Added
75
+
76
+ - **Nested iteration compilation** - Compiler now handles infinitely nested `<!-- each -->` blocks
77
+ - Recursive processing in `iteration_optimizer.rs` with depth counting for comment pair matching
78
+ - Nested loops compile to IIFEs (Immediately Invoked Function Expressions) with template literals
79
+ - Inner iterations inlined directly into outer batch functions for optimal performance
80
+ - Example: `<!-- each categories as cat --><!-- each cat.items as item -->` compiles to single optimized function
81
+ - **QuickJS JavaScript runtime** - Full expression evaluation at compile time
82
+ - Embedded QuickJS engine (`rquickjs = "0.6"`) for JavaScript evaluation in Rust
83
+ - No external dependencies - increases binary size by ~1-2MB
84
+ - Evaluates any JavaScript expression: `@[categories.length]`, `@[items[0]]`, `@[user.name.toUpperCase()]`
85
+ - State set in global scope: `Object.assign(globalThis, $)` matches Vibe runtime behavior
86
+ - Fast evaluation: ~160ms overhead for 35 files (~5ms per file)
87
+ - **Complete pre-rendering** - Zero FOUC with all bindings pre-rendered for SEO
88
+ - All `@[expression]` bindings evaluated and stamped into HTML at compile time
89
+ - Handles property access (`@[user.name]`), array methods (`@[categories.length]`), and complex expressions
90
+ - Nested iterations fully pre-rendered with merged state for each iteration context
91
+ - Falls back gracefully: undefined expressions left as `@[...]` for runtime hydration
92
+ - New test: `stamp_array_length` validates `.length` property evaluation
93
+
94
+ ### Changed
95
+
96
+ - **Value stamper rewrite** (`value_stamper.rs`) - Complete overhaul to use QuickJS
97
+ - Replaced JSON path resolution with JavaScript expression evaluation
98
+ - `eval_expression()` handles any valid JavaScript with state in scope
99
+ - `eval_array_path()` evaluates array paths for iteration rendering
100
+ - Iteration rendering creates merged state (parent + item + index) for nested context
101
+ - Removed manual property traversal code - JavaScript engine handles it all
102
+ - **Compiled iteration updates** - Always use compiled path when available (`iterate.js:364`)
103
+ - Previously only used compiled updates for edge cases (empty↔full, large arrays >100)
104
+ - Now uses compiled batch functions for ALL updates when manifest has `compiled.iterations.batchFn`
105
+ - Fixes issue where small array updates (3→4 items) fell through to incompatible runtime path
106
+ - Ensures consistent performance regardless of array size or transition type
107
+ - **State extraction improvements** (`state_extractor.rs`) - Better error handling
108
+ - Silently skips unparseable state objects instead of failing compilation
109
+ - Enables graceful degradation when state contains functions or complex expressions
110
+
111
+ ### Fixed
112
+
113
+ - Compiled iterations not updating when array size changes (e.g., add/remove items)
114
+ - Pre-rendering skipped for JavaScript expressions like `@[categories.length]`
115
+ - Nested iteration values showing as `@[item]` instead of actual data
116
+ - Runtime path attempting to handle compiled iterations incorrectly
117
+
118
+ ### Performance
119
+
120
+ - Pre-rendering adds ~160ms to compilation for 35 files (~435ms total, up from ~310ms)
121
+ - QuickJS evaluation: ~1-5ms per binding
122
+ - Compiled nested iterations: Same performance as shallow iterations (no recursion overhead at runtime)
123
+ - Zero runtime cost for pre-rendered bindings - HTML arrives with values already stamped
124
+
125
+ ---
126
+
127
+ ## [1.4.0] - 2026-02-09
128
+
129
+ ### Added
130
+
131
+ - **Iteration optimization** - Compiler now generates optimized batch functions for `<!-- each -->` loops
132
+ - New `iteration_optimizer.rs` module generates string-based batch render functions
133
+ - Provides 2-3x performance improvement for iteration rendering (15-17ms vs 40ms for 1000 rows)
134
+ - Only applies to shallow iterations (nested iterations still use runtime path)
135
+ - Compiled batch functions are stored in manifest and executed at runtime
136
+ - **Pre-compiled iterations runtime** (`runtime/pre-compiled-iterations.js`)
137
+ - Production implementation of compiled iteration rendering
138
+ - Uses pre-compiled batch functions from manifest generated at build time
139
+ - Based on the prototype in `_vibe-compiled-iteration-batch.js`
140
+ - Automatically falls back to runtime rendering for nested iterations or missing batch functions
141
+ - **Compiler configuration** - New `iterationsAsIs` flag
142
+ - Set to `true` in `vibe-compiler` config to skip iteration optimization
143
+ - Iterations pass through unchanged and are handled entirely by runtime
144
+ - Default: `false` (iterations are optimized)
145
+ - **Hyperspeed benchmark** - New benchmark page for measuring pre-compiled performance
146
+ - Tests iteration optimization with 1000 rows
147
+ - Compares runtime vs compiled iteration rendering
148
+ - Available at `e2e-runtime/hyperspeed-benchmark.html`
149
+ - **Compiler test coverage** for iteration compilation
150
+ - `tests/compiler/iterations/` - Tests iteration optimization is applied
151
+ - `tests/compiler/iterations-as-is/` - Tests `iterationsAsIs` flag skips optimization
152
+ - E2E tests verify compiled iterations render correctly
153
+
154
+ ### Changed
155
+
156
+ - **Renamed `runtime/hyperspeed.js` → `runtime/pre-compiled-manifest.js`**
157
+ - Better naming to reflect that it handles all pre-compiled features, not just "hyperspeed"
158
+ - Updated all imports and references throughout codebase
159
+ - **Enhanced manifest merging** - Compiler manifest now preserves compiled iteration data
160
+ - Iteration nodes retain `compiled.iterations.batchFn` from manifest during runtime merge
161
+ - Prevents compiled data from being discarded when merging with runtime tree
162
+ - **Debug logging improvements**
163
+ - Added manifest filename to debug output ("Loaded index.manifest.js, page is pre-compiled")
164
+ - Shows compiled feature count (e.g., "3 iterations optimized")
165
+ - Better visibility into which optimizations are active
166
+ - **Runtime iteration handling** - Iterations check for compiled batch functions before falling back
167
+ - `canUseCompiled()` determines if iteration can use batch function
168
+ - Falls back to runtime rendering for nested structures or missing compiled data
169
+ - Seamless integration between compiled and runtime paths
170
+
171
+ ### Fixed
172
+
173
+ - Runtime cleanup now properly handles compiled iteration nodes
174
+ - Manifest merge no longer discards compiled data from iteration nodes
175
+ - Debug logging visibility flag now correctly propagates through manifest loading
176
+ - Iteration restoration respects compiled batch functions
177
+
178
+ ---
179
+
180
+ ## [1.3.2] - 2025-02-06
181
+
182
+ ### Fixed
183
+
184
+ - **Package completeness**: Added missing `boot.js` to npm package
185
+ - File was missing from the "files" allowlist in package.json
186
+
187
+ ### Changed
188
+
189
+ - **Package strategy**: Switched from allowlist to denylist approach
190
+ - Removed "files" field from package.json
191
+ - Added `.npmignore` to exclude build artifacts (`target/`) and dev files
192
+ - Ensures all source files are included without manual maintenance
193
+
194
+ ---
195
+
3
196
  ## [1.3.1] - 2025-02-06
4
197
 
5
198
  ### Fixed
package/README.md CHANGED
@@ -124,6 +124,103 @@ Skip reactive processing for an element:
124
124
  <code dehydrate>@[this] displays literally</code>
125
125
  ```
126
126
 
127
+ ### Reserved Words & Gotchas
128
+
129
+ Vibe uses specific patterns and keywords that have special meaning. Avoid using these for other purposes to prevent unexpected behavior:
130
+
131
+ #### Classes & Attributes
132
+
133
+ - **`vibe-fouc`** — Class or attribute for FOUC (Flash of Unstyled Content) prevention. Automatically removed after hydration completes.
134
+ ```html
135
+ <body vibe-fouc> <!-- or class="vibe-fouc" -->
136
+ ```
137
+
138
+ - **`vibe-dehydrate`** — Class or attribute to skip reactive processing. Useful for displaying literal `@[...]` syntax in documentation.
139
+ ```html
140
+ <code vibe-dehydrate>@[variable]</code> <!-- or class="vibe-dehydrate" -->
141
+ ```
142
+
143
+ #### Element Names & Classes
144
+
145
+ - **`<component>`** — Element name for component system. Used with `src` attribute for runtime component loading, or as a wrapper for inlined components.
146
+ ```html
147
+ <component src="/path/to/component.html"></component>
148
+ ```
149
+
150
+ - **`class="component"`** — Alternative syntax for components using standard HTML elements. Useful for HTML validation or accessibility.
151
+ ```html
152
+ <div class="component" src="/path/to/component.html"></div>
153
+ ```
154
+
155
+ - **`<slot>`** — Element name for component content injection. Gets replaced with content passed between component tags.
156
+ ```html
157
+ <!-- In component file -->
158
+ <slot></slot>
159
+
160
+ <!-- Usage -->
161
+ <component src="...">
162
+ <p>This replaces the slot</p>
163
+ </component>
164
+ ```
165
+
166
+ #### Comment Syntax
167
+
168
+ - **`<!-- each -->`** / **`<!-- /each -->`** — Iteration block markers.
169
+ ```html
170
+ <!-- each items as item -->
171
+ <!-- /each -->
172
+ ```
173
+
174
+ - **`<!-- if -->`** / **`<!-- else -->`** / **`<!-- /if -->`** — Conditional block markers.
175
+ ```html
176
+ <!-- if condition -->
177
+ <!-- else -->
178
+ <!-- /if -->
179
+ ```
180
+
181
+ #### Binding Syntax
182
+
183
+ - **`@[...]`** — Reactive binding syntax. Reserved for state references.
184
+ ```html
185
+ <div>@[variable]</div>
186
+ ```
187
+
188
+ #### Global Properties
189
+
190
+ - **`window.$`** — Global reactive state object. All reactive data should be accessed through this.
191
+ ```javascript
192
+ window.$ = state({ count: 0 });
193
+ $.count++; // Triggers reactive updates
194
+ ```
195
+
196
+ - **`window.__vibeManifest`** — Internal manifest data. Used by the compiler for optimization. Don't modify.
197
+
198
+ - **`window.__vibeCompiling`** — Internal flag. Set to `true` when running in compiler context.
199
+
200
+ #### Data Attributes
201
+
202
+ - **`data-vibe-component-id`** — Internal attribute for component scoping. Automatically added to component elements. Don't use manually.
203
+
204
+ #### Event Names
205
+
206
+ - **`vibe:ready`** — Custom event fired when Vibe completes initial hydration.
207
+ ```javascript
208
+ document.addEventListener('vibe:ready', () => {
209
+ console.log('Vibe is ready');
210
+ });
211
+ ```
212
+
213
+ #### Special Attribute Meanings
214
+
215
+ - **`src`** on **`<component>`** or **`<div class="component">`** — Triggers runtime component fetching. Components without `src` are treated as inline wrappers.
216
+ ```html
217
+ <!-- Both work the same way -->
218
+ <component src="/components/card.html"></component>
219
+ <div class="component" src="/components/card.html"></div>
220
+ ```
221
+
222
+ - **`dehydrate`** — Attribute or class name to skip reactive processing (alias for `vibe-dehydrate`).
223
+
127
224
  ### Deep Reactivity
128
225
 
129
226
  Vibe uses recursive proxies to detect changes at any nesting level:
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*
package/boot.js ADDED
@@ -0,0 +1,45 @@
1
+ // Shared boot mechanism for vibe
2
+ // Used by both index.js (global state) and component.js (component state)
3
+
4
+ import main from './runtime/index.js';
5
+
6
+ let bootQueued = false;
7
+ let booted = false;
8
+
9
+ export const ensureBoot = () => {
10
+ if (booted || bootQueued) return;
11
+ bootQueued = true;
12
+
13
+ queueMicrotask(() => {
14
+ if (!booted) {
15
+ boot();
16
+ }
17
+ });
18
+ };
19
+
20
+ export const boot = () => {
21
+ if (booted) {
22
+ return window.$;
23
+ }
24
+ booted = true;
25
+
26
+ // Merge all state: global + components
27
+ const globalState = window.__vibeGlobalState || {};
28
+ const componentStates = window.__vibeComponents || {};
29
+
30
+ const mergedState = {
31
+ ...globalState,
32
+ ...componentStates,
33
+ };
34
+
35
+ // Get config and targetSelector (first caller wins)
36
+ const config = window.__vibeConfig || {};
37
+ const targetSelector = window.__vibeTargetSelector || '';
38
+
39
+ // Boot with merged state
40
+ window.$ = main(mergedState, config, targetSelector);
41
+
42
+ return window.$;
43
+ };
44
+
45
+ export const isBooted = () => booted;