bimba-cli 0.5.8 → 0.5.10

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 (2) hide show
  1. package/package.json +1 -1
  2. package/serve.js +46 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/serve.js CHANGED
@@ -70,10 +70,25 @@ const hmrClient = `
70
70
 
71
71
  function _applyUpdate(file, slots) {
72
72
  _queue = _queue.then(() => _doUpdate(file, slots)).catch(err => {
73
- console.error('[bimba HMR]', err);
73
+ // Safety net: any uncaught failure during HMR → full reload.
74
+ // Better to lose state than to leave a broken page.
75
+ console.error('[bimba HMR] reload due to error:', err);
76
+ location.reload();
74
77
  });
75
78
  }
76
79
 
80
+ // Walk a subtree and call disconnectedCallback on each custom element.
81
+ // Used before destroying inner DOM on the shifted path so imba/web-component
82
+ // teardown logic (event listeners, observers, etc.) runs cleanly.
83
+ function _disconnectDescendants(root) {
84
+ const all = root.querySelectorAll('*');
85
+ for (const el of all) {
86
+ if (el.tagName.includes('-')) {
87
+ try { el.disconnectedCallback && el.disconnectedCallback(); } catch(_) {}
88
+ }
89
+ }
90
+ }
91
+
77
92
  async function _doUpdate(file, slots) {
78
93
  clearError();
79
94
 
@@ -115,11 +130,23 @@ const hmrClient = `
115
130
  // that imba's runtime uses for lifecycle), clear innerHTML,
116
131
  // restore state, re-render. Loses inner DOM state for instances
117
132
  // of the patched tags, but preserves their instance fields.
133
+ // Snapshot child counts of the patched tags BEFORE commit. On the
134
+ // stable path, child count must not grow — if it does, it means slot
135
+ // stabilization failed for this edit and imba's renderer appended
136
+ // fresh children alongside the old ones. That's the duplication bug
137
+ // we cannot recover from in-place → trigger a reload.
138
+ const childSnap = new Map();
139
+ for (const tag of collected) {
140
+ const list = document.querySelectorAll(tag);
141
+ for (const el of list) childSnap.set(el, el.children.length);
142
+ }
143
+
118
144
  if (slots === 'shifted') {
119
145
  for (const tag of collected) {
120
146
  document.querySelectorAll(tag).forEach(el => {
121
147
  const state = {};
122
148
  for (const k of Object.keys(el)) state[k] = el[k];
149
+ _disconnectDescendants(el);
123
150
  for (const sym of Object.getOwnPropertySymbols(el)) {
124
151
  if (Symbol.keyFor(sym) !== undefined) continue;
125
152
  try { delete el[sym]; } catch(_) {}
@@ -127,12 +154,30 @@ const hmrClient = `
127
154
  el.innerHTML = '';
128
155
  Object.assign(el, state);
129
156
  try { el.render && el.render(); } catch(_) {}
157
+ // Re-fire lifecycle for the top tag itself: imba compiles
158
+ // "def mount" to a mount() instance method, and standard
159
+ // connectedCallback may also matter for descendants created
160
+ // by render(). The element is still attached to its parent,
161
+ // so we just call them directly.
162
+ try { el.connectedCallback && el.connectedCallback(); } catch(_) {}
163
+ try { el.mount && el.mount(); } catch(_) {}
130
164
  });
131
165
  }
132
166
  }
133
167
 
134
168
  if (typeof imba !== 'undefined') imba.commit();
135
169
 
170
+ // Stable-path duplication check.
171
+ if (slots !== 'shifted') {
172
+ for (const [el, before] of childSnap) {
173
+ if (el.children.length > before) {
174
+ console.warn('[bimba HMR] slot stabilization failed, reloading');
175
+ location.reload();
176
+ return;
177
+ }
178
+ }
179
+ }
180
+
136
181
  // Smart body dedupe: remove only elements that were ADDED during this
137
182
  // HMR cycle and whose tag already existed in body before. This catches
138
183
  // accidental re-mounts from top-level imba.mount() re-runs, but