webspresso 0.0.112 → 0.0.113

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.
@@ -157,6 +157,172 @@ function createFragmentCoordinator(options = {}) {
157
157
  let isDisposed = false;
158
158
  let lifecycleListenersAttached = false;
159
159
 
160
+ // Per-version patch options (mode, limit, target, removeKeys, onBeforePatch, onAfterPatch)
161
+ const pendingPatchOptsByKey = new Map();
162
+
163
+ // Lazy fragment IntersectionObserver state
164
+ let lazyObserver = null;
165
+ const observedLazyEls = new WeakSet();
166
+
167
+ function getLazyObserver() {
168
+ if (lazyObserver) return lazyObserver;
169
+ const win =
170
+ typeof window !== 'undefined'
171
+ ? window
172
+ : typeof globalThis !== 'undefined'
173
+ ? globalThis
174
+ : null;
175
+ const IOCtor = win && typeof win.IntersectionObserver === 'function' ? win.IntersectionObserver : null;
176
+ if (!IOCtor) return null;
177
+ lazyObserver = new IOCtor((entries) => {
178
+ if (isDisposed) return;
179
+ const visibleIds = [];
180
+ for (let i = 0; i < entries.length; i++) {
181
+ const entry = entries[i];
182
+ if (!entry || !entry.isIntersecting) continue;
183
+ const targetEl = entry.target;
184
+ try {
185
+ lazyObserver.unobserve(targetEl);
186
+ } catch (_e) {}
187
+ const fragId = targetEl && targetEl.getAttribute ? targetEl.getAttribute('data-w-fragment-id') : null;
188
+ const inst = fragId ? instances.get(fragId) : null;
189
+ if (inst && inst.lazy && inst.lazyState === 'idle') {
190
+ inst.lazyState = 'loading';
191
+ visibleIds.push(inst.instanceId);
192
+ }
193
+ }
194
+ if (visibleIds.length > 0) {
195
+ scheduleAutoRefresh(visibleIds, 'lazy');
196
+ }
197
+ });
198
+ return lazyObserver;
199
+ }
200
+
201
+ function observeLazyFragment(inst) {
202
+ if (!inst || !inst.el || !inst.lazy || inst.lazyState !== 'idle') return;
203
+ if (observedLazyEls.has(inst.el)) return;
204
+ const obs = getLazyObserver();
205
+ if (!obs) return;
206
+ observedLazyEls.add(inst.el);
207
+ try {
208
+ obs.observe(inst.el);
209
+ } catch (_e) {}
210
+ }
211
+
212
+ function unobserveLazyFragment(inst) {
213
+ if (!inst || !inst.el || !lazyObserver) return;
214
+ try {
215
+ lazyObserver.unobserve(inst.el);
216
+ } catch (_e) {}
217
+ }
218
+
219
+ function getListItemKey(node) {
220
+ if (!node || node.nodeType !== 1 || typeof node.getAttribute !== 'function') return null;
221
+ if (node.id) return `id:${node.id}`;
222
+ const wKey = node.getAttribute('data-w-key');
223
+ if (wKey) return `wkey:${wKey}`;
224
+ return null;
225
+ }
226
+
227
+ function patchIncrementalList(fragEl, html, opts = {}) {
228
+ const doc = fragEl.ownerDocument || (typeof document !== 'undefined' ? document : null);
229
+ const parsed = datastar && typeof datastar.parseHtmlFragment === 'function'
230
+ ? datastar.parseHtmlFragment(html, doc)
231
+ : null;
232
+ if (!parsed) return false;
233
+
234
+ const targetSel = opts.target || null;
235
+ const container =
236
+ (targetSel && fragEl.querySelector ? fragEl.querySelector(targetSel) : null) ||
237
+ (fragEl.querySelector ? fragEl.querySelector('[data-w-list]') : null) ||
238
+ fragEl;
239
+ const incomingContainer =
240
+ (targetSel && parsed.querySelector ? parsed.querySelector(targetSel) : null) ||
241
+ (parsed.querySelector ? parsed.querySelector('[data-w-list]') : null) ||
242
+ parsed;
243
+
244
+ // 1. Remove explicitly requested keys if any
245
+ if (isArray(opts.removeKeys) && opts.removeKeys.length > 0) {
246
+ const removeSet = new Set(opts.removeKeys.map(String));
247
+ const curChildren = Array.from(container.children || []);
248
+ for (let i = 0; i < curChildren.length; i++) {
249
+ const child = curChildren[i];
250
+ const rawKey = child.getAttribute('data-w-key') || child.id || '';
251
+ const fullKey = getListItemKey(child);
252
+ if (removeSet.has(rawKey) || (fullKey && removeSet.has(fullKey))) {
253
+ if (typeof opts.onBeforeNodeRemove === 'function') opts.onBeforeNodeRemove(child);
254
+ container.removeChild(child);
255
+ }
256
+ }
257
+ }
258
+
259
+ // 2. Index existing keyed items in container
260
+ const existingByKey = new Map();
261
+ const existingChildren = Array.from(container.children || []);
262
+ for (let i = 0; i < existingChildren.length; i++) {
263
+ const k = getListItemKey(existingChildren[i]);
264
+ if (k && !existingByKey.has(k)) {
265
+ existingByKey.set(k, existingChildren[i]);
266
+ }
267
+ }
268
+
269
+ const incomingItems = Array.from(incomingContainer.children || []);
270
+ const mode = opts.mode === 'prepend' ? 'prepend' : 'append';
271
+ const itemsToInsert = mode === 'prepend' ? incomingItems.slice().reverse() : incomingItems;
272
+
273
+ for (let i = 0; i < itemsToInsert.length; i++) {
274
+ const incoming = itemsToInsert[i];
275
+ if (incoming.nodeType !== 1) continue;
276
+ const key = getListItemKey(incoming);
277
+ const existingMatch = key ? existingByKey.get(key) : null;
278
+
279
+ if (existingMatch && existingMatch.tagName === incoming.tagName) {
280
+ // Update existing keyed item in place without duplicating
281
+ if (datastar && typeof datastar.morphDomTrees === 'function') {
282
+ datastar.morphDomTrees(existingMatch, incoming, {
283
+ onBeforeNodeRemove: opts.onBeforeNodeRemove,
284
+ onNodeAdded: opts.onNodeAdded,
285
+ });
286
+ }
287
+ continue;
288
+ }
289
+
290
+ const imported = container.ownerDocument
291
+ ? container.ownerDocument.importNode(incoming, true)
292
+ : incoming.cloneNode(true);
293
+
294
+ if (mode === 'prepend') {
295
+ const firstRef = (container.children && container.children[0]) || container.firstChild || null;
296
+ container.insertBefore(imported, firstRef);
297
+ } else {
298
+ container.appendChild(imported);
299
+ }
300
+
301
+ if (key) {
302
+ existingByKey.set(key, imported);
303
+ }
304
+ if (typeof opts.onNodeAdded === 'function') {
305
+ opts.onNodeAdded(imported);
306
+ }
307
+ }
308
+
309
+ // 3. Enforce optional retained item limit
310
+ const limitAttr = container.getAttribute ? container.getAttribute('data-w-limit') : null;
311
+ const rawLimit = opts.limit !== undefined ? opts.limit : limitAttr;
312
+ const limit = rawLimit !== null && rawLimit !== undefined && rawLimit !== '' ? Number(rawLimit) : 0;
313
+ if (Number.isFinite(limit) && limit > 0) {
314
+ while (container.children && container.children.length > limit) {
315
+ const ch = container.children;
316
+ const victim = mode === 'prepend' ? ch[ch.length - 1] : ch[0];
317
+ if (!victim) break;
318
+ if (typeof opts.onBeforeNodeRemove === 'function') opts.onBeforeNodeRemove(victim);
319
+ container.removeChild(victim);
320
+ }
321
+ }
322
+
323
+ return true;
324
+ }
325
+
160
326
  function isDocumentHidden() {
161
327
  const root = getRoot();
162
328
  const doc = root && root.nodeType === 9 ? root : root && root.ownerDocument ? root.ownerDocument : typeof document !== 'undefined' ? document : null;
@@ -260,7 +426,12 @@ function createFragmentCoordinator(options = {}) {
260
426
  try {
261
427
  await executeBatchRefresh(targets);
262
428
  } catch (_err) {
263
- // Background auto-refetch errors are non-fatal
429
+ // Background auto-refetch errors are non-fatal; reset lazyState if still unloaded so retry/re-observe works
430
+ for (const inst of deduped) {
431
+ if (inst.lazy && inst.lazyState === 'loading') {
432
+ inst.lazyState = 'idle';
433
+ }
434
+ }
264
435
  }
265
436
  }
266
437
 
@@ -272,7 +443,7 @@ function createFragmentCoordinator(options = {}) {
272
443
  if (reason) pendingAutoReasons.add(reason);
273
444
  if (pendingAutoInstanceIds.size === 0) return;
274
445
 
275
- if (reason === 'mount') {
446
+ if (reason === 'mount' || reason === 'lazy') {
276
447
  if (!autoBatchScheduled) {
277
448
  autoBatchScheduled = true;
278
449
  Promise.resolve().then(flushAutoRefresh);
@@ -469,6 +640,9 @@ function createFragmentCoordinator(options = {}) {
469
640
  ensureLifecycleListeners();
470
641
  }
471
642
 
643
+ const rawLazyAttr = el.getAttribute('data-w-lazy');
644
+ const isLazyUnloaded = Boolean(rawLazyAttr && rawLazyAttr !== 'false' && rawLazyAttr !== 'loaded');
645
+
472
646
  if (existing) {
473
647
  existing.name = name;
474
648
  existing.pagePath = pagePath;
@@ -489,6 +663,9 @@ function createFragmentCoordinator(options = {}) {
489
663
  existing._rawStale = rawStale;
490
664
  existing._rawInterval = rawInterval;
491
665
  syncInstanceInterval(existing);
666
+ if (existing.lazy && existing.lazyState === 'idle') {
667
+ observeLazyFragment(existing);
668
+ }
492
669
  } else {
493
670
  const created = {
494
671
  instanceId,
@@ -511,6 +688,9 @@ function createFragmentCoordinator(options = {}) {
511
688
  refetchIntervalInBackground,
512
689
  intervalTimer: null,
513
690
  activeIntervalMs: 0,
691
+ lazy: isLazyUnloaded,
692
+ lazyState: isLazyUnloaded ? 'idle' : 'loaded',
693
+ staleWhileUnloaded: false,
514
694
  _rawInput: rawInput,
515
695
  _rawDepends: rawDepends,
516
696
  _rawStale: rawStale,
@@ -518,7 +698,9 @@ function createFragmentCoordinator(options = {}) {
518
698
  };
519
699
  instances.set(instanceId, created);
520
700
  syncInstanceInterval(created);
521
- if (pendingRestoredInput) {
701
+ if (isLazyUnloaded) {
702
+ observeLazyFragment(created);
703
+ } else if (pendingRestoredInput) {
522
704
  Promise.resolve().then(() => {
523
705
  if (!isDisposed && created.version === 0 && instances.get(instanceId) === created) {
524
706
  refresh(instanceId, pendingRestoredInput).catch(() => {});
@@ -558,6 +740,7 @@ function createFragmentCoordinator(options = {}) {
558
740
  !seenIds.has(id) || (inst.el && inst.el.isConnected === false);
559
741
  if (disconnected) {
560
742
  clearInstanceInterval(inst);
743
+ unobserveLazyFragment(inst);
561
744
  if (inst.abortController) {
562
745
  try {
563
746
  inst.abortController.abort();
@@ -603,6 +786,21 @@ function createFragmentCoordinator(options = {}) {
603
786
  const html = fragmentPayload && typeof fragmentPayload.html === 'string' ? fragmentPayload.html : '';
604
787
  if (!html) return false;
605
788
 
789
+ const patchKey = `${inst.instanceId}:${reqVersion}`;
790
+ const patchOpts = pendingPatchOptsByKey.get(patchKey) || null;
791
+ if (patchOpts) {
792
+ pendingPatchOptsByKey.delete(patchKey);
793
+ }
794
+
795
+ if (typeof options.onBeforePatch === 'function') {
796
+ options.onBeforePatch(inst.el, inst);
797
+ }
798
+ if (patchOpts && typeof patchOpts.onBeforePatch === 'function') {
799
+ try {
800
+ patchOpts.onBeforePatch(inst.el, inst);
801
+ } catch (_e) {}
802
+ }
803
+
606
804
  const patchStart = Date.now();
607
805
 
608
806
  const committedInput =
@@ -629,7 +827,17 @@ function createFragmentCoordinator(options = {}) {
629
827
  }
630
828
  }
631
829
 
632
- if (datastar && typeof datastar.patchFragmentElement === 'function') {
830
+ const patchMode = fragmentPayload.mode || (patchOpts && patchOpts.mode) || 'replace';
831
+ if (patchMode === 'append' || patchMode === 'prepend') {
832
+ patchIncrementalList(inst.el, html, {
833
+ mode: patchMode,
834
+ limit: fragmentPayload.limit !== undefined ? fragmentPayload.limit : patchOpts && patchOpts.limit,
835
+ target: fragmentPayload.target || (patchOpts && patchOpts.target),
836
+ removeKeys: fragmentPayload.removeKeys || (patchOpts && patchOpts.removeKeys),
837
+ onBeforeNodeRemove: options.onBeforeNodeRemove,
838
+ onNodeAdded: options.onNodeAdded,
839
+ });
840
+ } else if (datastar && typeof datastar.patchFragmentElement === 'function') {
633
841
  datastar.patchFragmentElement(inst.el, html, {
634
842
  onBeforeNodeRemove: options.onBeforeNodeRemove,
635
843
  onNodeAdded: options.onNodeAdded,
@@ -638,6 +846,17 @@ function createFragmentCoordinator(options = {}) {
638
846
  inst.el.innerHTML = html;
639
847
  }
640
848
 
849
+ // Clear lazy unloaded markers once a real patch is applied
850
+ if (inst.lazy || inst.lazyState !== 'loaded') {
851
+ inst.lazy = false;
852
+ inst.lazyState = 'loaded';
853
+ inst.staleWhileUnloaded = false;
854
+ unobserveLazyFragment(inst);
855
+ if (inst.el && typeof inst.el.removeAttribute === 'function') {
856
+ inst.el.removeAttribute('data-w-lazy');
857
+ }
858
+ }
859
+
641
860
  const patchMs = Math.max(0, Date.now() - patchStart);
642
861
  const now = Date.now();
643
862
  inst.lastUpdatedAt = now;
@@ -653,6 +872,11 @@ function createFragmentCoordinator(options = {}) {
653
872
  if (typeof options.onAfterPatch === 'function') {
654
873
  options.onAfterPatch(inst.el, inst);
655
874
  }
875
+ if (patchOpts && typeof patchOpts.onAfterPatch === 'function') {
876
+ try {
877
+ patchOpts.onAfterPatch(inst.el, inst);
878
+ } catch (_e) {}
879
+ }
656
880
 
657
881
  if (debug) {
658
882
  debug.recordChainPatch(chainId, {
@@ -704,6 +928,9 @@ function createFragmentCoordinator(options = {}) {
704
928
  if (!parentInst || !parentInst.el) continue;
705
929
  for (const otherInst of instances.values()) {
706
930
  if (otherInst !== parentInst && otherInst.el && isStrictAncestor(parentInst.el, otherInst.el)) {
931
+ // Supersede any older in-flight refresh on the nested child so a late child response
932
+ // cannot overwrite the newer parent+child snapshot.
933
+ otherInst.version += 1;
707
934
  if (otherInst.input && typeof otherInst.input === 'object') {
708
935
  childInputs[otherInst.instanceId] = { ...otherInst.input };
709
936
  if (!childInputs[otherInst.name]) {
@@ -908,8 +1135,8 @@ function createFragmentCoordinator(options = {}) {
908
1135
  debug.trackRequestEnd(reqId, { status: 'completed', httpStatus: response.status });
909
1136
  }
910
1137
 
911
- lastFailedBatch = null;
912
1138
  const rawFragments = (body && body.fragments) || {};
1139
+ const rawErrors = (body && body.errors && typeof body.errors === 'object' ? body.errors : {}) || {};
913
1140
  const resultsMap = {};
914
1141
  if (isArray(rawFragments)) {
915
1142
  for (const item of rawFragments) {
@@ -922,12 +1149,42 @@ function createFragmentCoordinator(options = {}) {
922
1149
  Object.assign(resultsMap, rawFragments);
923
1150
  }
924
1151
  const refreshedIds = [];
1152
+ const batchErrors = [];
1153
+ const failedTargets = [];
925
1154
  let staleDiscardCount = 0;
926
1155
  let totalLoadMs = 0;
927
1156
  let totalRenderMs = 0;
928
1157
 
929
1158
  for (const target of targets) {
930
1159
  const inst = instances.get(target.instanceId);
1160
+ const fragError =
1161
+ rawErrors[target.instanceId] ||
1162
+ rawErrors[target.name] ||
1163
+ (resultsMap[target.instanceId] && resultsMap[target.instanceId].error) ||
1164
+ (resultsMap[target.name] && resultsMap[target.name].error) ||
1165
+ null;
1166
+ if (fragError) {
1167
+ const errObj =
1168
+ fragError instanceof Error
1169
+ ? fragError
1170
+ : Object.assign(
1171
+ new Error(
1172
+ (fragError && typeof fragError === 'object' && fragError.message) ||
1173
+ String(fragError) ||
1174
+ `Fragment "${target.name}" failed to refresh`
1175
+ ),
1176
+ {
1177
+ status: (fragError && fragError.status) || 500,
1178
+ code: (fragError && fragError.code) || 'FRAGMENT_REFRESH_ERROR',
1179
+ instanceId: target.instanceId,
1180
+ fragmentName: target.name,
1181
+ }
1182
+ );
1183
+ batchErrors.push(errObj);
1184
+ failedTargets.push(target);
1185
+ continue;
1186
+ }
1187
+
931
1188
  const fragResult = resultsMap[target.instanceId] || resultsMap[target.name];
932
1189
  if (!inst || !fragResult) continue;
933
1190
  if (target.reqVersion < inst.version) {
@@ -949,13 +1206,33 @@ function createFragmentCoordinator(options = {}) {
949
1206
  }
950
1207
 
951
1208
  const allSuperseded =
952
- refreshedIds.length === 0 && staleDiscardCount > 0 && staleDiscardCount === targets.length;
1209
+ refreshedIds.length === 0 &&
1210
+ batchErrors.length === 0 &&
1211
+ staleDiscardCount > 0 &&
1212
+ staleDiscardCount === targets.length;
1213
+
1214
+ if (refreshedIds.length > 0 && lastFailedBatch && isArray(lastFailedBatch.targets)) {
1215
+ const refreshedSet = new Set(refreshedIds);
1216
+ const remainingFailed = lastFailedBatch.targets.filter((t) => !refreshedSet.has(t.instanceId));
1217
+ lastFailedBatch =
1218
+ remainingFailed.length > 0
1219
+ ? { targets: remainingFailed, chainIds: lastFailedBatch.chainIds }
1220
+ : null;
1221
+ }
1222
+
1223
+ if (batchErrors.length > 0) {
1224
+ lastFailedBatch = {
1225
+ targets: failedTargets,
1226
+ chainIds,
1227
+ };
1228
+ }
953
1229
 
954
1230
  for (const cid of chainIds) {
955
1231
  if (debug) {
956
1232
  debug.recordChainRefresh(cid, {
957
1233
  fragmentIds: refreshedIds,
958
- status: allSuperseded ? 'superseded' : 'ok',
1234
+ status: batchErrors.length > 0 ? (refreshedIds.length > 0 ? 'partial-error' : 'error') : allSuperseded ? 'superseded' : 'ok',
1235
+ error: batchErrors[0] || null,
959
1236
  loadMs: totalLoadMs,
960
1237
  renderMs: totalRenderMs,
961
1238
  totalServerMs: body && isNumber(body.totalMs) ? body.totalMs : null,
@@ -977,6 +1254,20 @@ function createFragmentCoordinator(options = {}) {
977
1254
  };
978
1255
  }
979
1256
 
1257
+ if (batchErrors.length > 0) {
1258
+ return {
1259
+ ok: false,
1260
+ partial: refreshedIds.length > 0,
1261
+ refreshed: refreshedIds,
1262
+ errors: batchErrors,
1263
+ error: batchErrors[0],
1264
+ timings: {
1265
+ loadMs: totalLoadMs,
1266
+ renderMs: totalRenderMs,
1267
+ },
1268
+ };
1269
+ }
1270
+
980
1271
  return {
981
1272
  ok: true,
982
1273
  refreshed: refreshedIds,
@@ -1017,13 +1308,18 @@ function createFragmentCoordinator(options = {}) {
1017
1308
  for (const inst of instances.values()) {
1018
1309
  let matched = false;
1019
1310
  for (const tag of tagsToFlush) {
1020
- if (inst.dependsOn.has(tag) || inst.name === tag) {
1311
+ if (inst.dependsOn.has(tag) || inst.name === tag || inst.instanceId === tag) {
1021
1312
  matched = true;
1022
1313
  break;
1023
1314
  }
1024
1315
  }
1025
1316
  if (matched) {
1026
- matchingInstances.push(inst);
1317
+ if (inst.lazy && inst.lazyState === 'idle') {
1318
+ // Do not fetch off-screen unloaded lazy fragments on invalidation; mark stale for when visible
1319
+ inst.staleWhileUnloaded = true;
1320
+ } else {
1321
+ matchingInstances.push(inst);
1322
+ }
1027
1323
  }
1028
1324
  }
1029
1325
 
@@ -1179,6 +1475,29 @@ function createFragmentCoordinator(options = {}) {
1179
1475
  inst.abortController = abortController;
1180
1476
  inst.version += 1;
1181
1477
 
1478
+ if (inst.lazy && inst.lazyState === 'idle') {
1479
+ inst.lazyState = 'loading';
1480
+ unobserveLazyFragment(inst);
1481
+ }
1482
+
1483
+ if (
1484
+ refreshOpts.mode ||
1485
+ refreshOpts.limit !== undefined ||
1486
+ refreshOpts.target ||
1487
+ refreshOpts.removeKeys ||
1488
+ typeof refreshOpts.onBeforePatch === 'function' ||
1489
+ typeof refreshOpts.onAfterPatch === 'function'
1490
+ ) {
1491
+ pendingPatchOptsByKey.set(`${inst.instanceId}:${inst.version}`, {
1492
+ mode: refreshOpts.mode || null,
1493
+ limit: refreshOpts.limit,
1494
+ target: refreshOpts.target,
1495
+ removeKeys: refreshOpts.removeKeys,
1496
+ onBeforePatch: refreshOpts.onBeforePatch,
1497
+ onAfterPatch: refreshOpts.onAfterPatch,
1498
+ });
1499
+ }
1500
+
1182
1501
  const candidateInput =
1183
1502
  nextInput && typeof nextInput === 'object'
1184
1503
  ? refreshOpts.replaceInput
@@ -1207,6 +1526,21 @@ function createFragmentCoordinator(options = {}) {
1207
1526
  const result = await executeBatchRefresh(targets, [], abortController.signal, refreshOpts.timeout);
1208
1527
  clearOwnedAbortControllers();
1209
1528
  const enrichedResult = { ok: true, ...result };
1529
+ if (enrichedResult.ok === false && !enrichedResult.canceled && !enrichedResult.superseded && enrichedResult.error) {
1530
+ const failHandler = refreshOpts.onFailure || refreshOpts.onError;
1531
+ if (typeof failHandler === 'function') {
1532
+ await failHandler(enrichedResult.error);
1533
+ }
1534
+ if (typeof refreshOpts.onSettled === 'function') {
1535
+ await refreshOpts.onSettled(enrichedResult, enrichedResult.error);
1536
+ } else if (typeof refreshOpts.onComplete === 'function') {
1537
+ await refreshOpts.onComplete(enrichedResult, enrichedResult.error);
1538
+ }
1539
+ if (refreshOpts.throwOnError === true) {
1540
+ throw enrichedResult.error;
1541
+ }
1542
+ return enrichedResult;
1543
+ }
1210
1544
  if (!enrichedResult.canceled && !enrichedResult.superseded && enrichedResult.ok !== false) {
1211
1545
  if (typeof refreshOpts.onSuccess === 'function') {
1212
1546
  await refreshOpts.onSuccess(enrichedResult);
@@ -1277,6 +1611,24 @@ function createFragmentCoordinator(options = {}) {
1277
1611
  return executeBatchRefresh(retryTargets, chainIds);
1278
1612
  }
1279
1613
 
1614
+ function resolveInstanceByIdOrName(idOrName) {
1615
+ if (!idOrName) return null;
1616
+ const direct = instances.get(idOrName);
1617
+ if (direct && (!direct.el || direct.el.isConnected !== false)) {
1618
+ return direct;
1619
+ }
1620
+ const defaultInst = instances.get(`${idOrName}:default`);
1621
+ if (defaultInst && (!defaultInst.el || defaultInst.el.isConnected !== false)) {
1622
+ return defaultInst;
1623
+ }
1624
+ for (const inst of instances.values()) {
1625
+ if (inst.name === idOrName && (!inst.el || inst.el.isConnected !== false)) {
1626
+ return inst;
1627
+ }
1628
+ }
1629
+ return null;
1630
+ }
1631
+
1280
1632
  return {
1281
1633
  syncDomInstances,
1282
1634
  invalidate,
@@ -1288,17 +1640,28 @@ function createFragmentCoordinator(options = {}) {
1288
1640
  scanDomInstances();
1289
1641
  for (const item of items) {
1290
1642
  const key = item.instanceId || item.name;
1291
- const inst = instances.get(key) || Array.from(instances.values()).find((i) => i.name === key);
1292
- if (inst) {
1293
- if (item.input && typeof item.input === 'object') {
1294
- inst.input = { ...item.input };
1295
- if (inst.el && typeof inst.el.setAttribute === 'function') {
1296
- const serializedInput = JSON.stringify(inst.input);
1297
- inst.el.setAttribute('data-w-fragment-input', serializedInput);
1298
- inst._rawInput = serializedInput;
1643
+ const matchedList = [];
1644
+ if (item.instanceId && instances.has(item.instanceId)) {
1645
+ matchedList.push(instances.get(item.instanceId));
1646
+ } else {
1647
+ for (const inst of instances.values()) {
1648
+ if (inst.instanceId === key || inst.name === key) {
1649
+ matchedList.push(inst);
1299
1650
  }
1300
1651
  }
1301
- pendingTags.add(inst.name);
1652
+ }
1653
+ if (matchedList.length > 0) {
1654
+ for (const inst of matchedList) {
1655
+ if (item.input && typeof item.input === 'object') {
1656
+ inst.input = { ...item.input };
1657
+ if (inst.el && typeof inst.el.setAttribute === 'function') {
1658
+ const serializedInput = JSON.stringify(inst.input);
1659
+ inst.el.setAttribute('data-w-fragment-input', serializedInput);
1660
+ inst._rawInput = serializedInput;
1661
+ }
1662
+ }
1663
+ pendingTags.add(item.instanceId ? inst.instanceId : inst.name);
1664
+ }
1302
1665
  } else if (key) {
1303
1666
  pendingTags.add(String(key).trim());
1304
1667
  }
@@ -1311,7 +1674,7 @@ function createFragmentCoordinator(options = {}) {
1311
1674
  const matching = [];
1312
1675
  for (const inst of instances.values()) {
1313
1676
  for (const t of tagSet) {
1314
- if (inst.dependsOn.has(t) || inst.name === t) {
1677
+ if (inst.dependsOn.has(t) || inst.name === t || inst.instanceId === t) {
1315
1678
  matching.push(inst);
1316
1679
  break;
1317
1680
  }
@@ -1321,17 +1684,19 @@ function createFragmentCoordinator(options = {}) {
1321
1684
  },
1322
1685
  retryFailed,
1323
1686
  retryLastFailed: retryFailed,
1324
- getInstance(instanceId) {
1325
- const hit = instances.get(instanceId);
1326
- if (hit && (!hit.el || hit.el.isConnected !== false)) {
1687
+ getInstance(instanceIdOrName) {
1688
+ const hit = resolveInstanceByIdOrName(instanceIdOrName);
1689
+ if (hit) {
1327
1690
  return hit;
1328
1691
  }
1329
1692
  scanDomInstances();
1330
- return instances.get(instanceId) || null;
1693
+ return resolveInstanceByIdOrName(instanceIdOrName);
1331
1694
  },
1332
- getInstances() {
1695
+ getInstances(nameOrId) {
1333
1696
  scanDomInstances();
1334
- return Array.from(instances.values());
1697
+ const all = Array.from(instances.values());
1698
+ if (!nameOrId) return all;
1699
+ return all.filter((inst) => inst.instanceId === nameOrId || inst.name === nameOrId);
1335
1700
  },
1336
1701
  getStoredInput(instanceIdOrName, storageType = 'session') {
1337
1702
  const store = storageType === 'local' ? defaultStorage.local : defaultStorage.session;
@@ -1368,6 +1733,13 @@ function createFragmentCoordinator(options = {}) {
1368
1733
  }
1369
1734
  pendingAutoInstanceIds.clear();
1370
1735
  pendingAutoReasons.clear();
1736
+ pendingPatchOptsByKey.clear();
1737
+ if (lazyObserver && typeof lazyObserver.disconnect === 'function') {
1738
+ try {
1739
+ lazyObserver.disconnect();
1740
+ } catch (_e) {}
1741
+ lazyObserver = null;
1742
+ }
1371
1743
  removeLifecycleListeners();
1372
1744
  for (const inst of instances.values()) {
1373
1745
  clearInstanceInterval(inst);
@@ -192,9 +192,21 @@ function createClientRuntime(options = {}) {
192
192
  controllers.mountSubtree(addedEl);
193
193
  }
194
194
  },
195
- onAfterPatch(patchedEl) {
195
+ onBeforePatch(patchedEl, inst) {
196
+ if (controllers) {
197
+ controllers.notifyBeforePatch(patchedEl, {
198
+ fragmentId: inst && inst.instanceId ? inst.instanceId : null,
199
+ fragmentName: inst && inst.name ? inst.name : null,
200
+ });
201
+ }
202
+ },
203
+ onAfterPatch(patchedEl, inst) {
196
204
  if (controllers) {
197
205
  controllers.mountSubtree(patchedEl);
206
+ controllers.notifyAfterPatch(patchedEl, {
207
+ fragmentId: inst && inst.instanceId ? inst.instanceId : null,
208
+ fragmentName: inst && inst.name ? inst.name : null,
209
+ });
198
210
  }
199
211
  },
200
212
  });