wu-framework 1.1.6 โ†’ 1.1.8

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 (90) hide show
  1. package/README.md +511 -977
  2. package/dist/wu-framework.cjs.js +3 -1
  3. package/dist/wu-framework.cjs.js.map +1 -0
  4. package/dist/wu-framework.dev.js +7533 -2761
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +3 -0
  7. package/dist/wu-framework.esm.js.map +1 -0
  8. package/dist/wu-framework.umd.js +3 -1
  9. package/dist/wu-framework.umd.js.map +1 -0
  10. package/integrations/astro/README.md +127 -0
  11. package/integrations/astro/WuApp.astro +63 -0
  12. package/integrations/astro/WuShell.astro +39 -0
  13. package/integrations/astro/index.js +68 -0
  14. package/integrations/astro/package.json +38 -0
  15. package/integrations/astro/types.d.ts +53 -0
  16. package/package.json +94 -74
  17. package/src/adapters/angular/ai.js +30 -0
  18. package/src/adapters/angular/index.d.ts +154 -0
  19. package/src/adapters/angular/index.js +932 -0
  20. package/src/adapters/angular.d.ts +3 -154
  21. package/src/adapters/angular.js +3 -813
  22. package/src/adapters/index.js +35 -24
  23. package/src/adapters/lit/ai.js +20 -0
  24. package/src/adapters/lit/index.d.ts +120 -0
  25. package/src/adapters/lit/index.js +721 -0
  26. package/src/adapters/lit.d.ts +3 -120
  27. package/src/adapters/lit.js +3 -726
  28. package/src/adapters/preact/ai.js +33 -0
  29. package/src/adapters/preact/index.d.ts +108 -0
  30. package/src/adapters/preact/index.js +661 -0
  31. package/src/adapters/preact.d.ts +3 -108
  32. package/src/adapters/preact.js +3 -665
  33. package/src/adapters/react/ai.js +135 -0
  34. package/src/adapters/react/index.d.ts +246 -0
  35. package/src/adapters/react/index.js +689 -0
  36. package/src/adapters/react.d.ts +3 -212
  37. package/src/adapters/react.js +3 -513
  38. package/src/adapters/shared.js +64 -0
  39. package/src/adapters/solid/ai.js +32 -0
  40. package/src/adapters/solid/index.d.ts +101 -0
  41. package/src/adapters/solid/index.js +586 -0
  42. package/src/adapters/solid.d.ts +3 -101
  43. package/src/adapters/solid.js +3 -591
  44. package/src/adapters/svelte/ai.js +31 -0
  45. package/src/adapters/svelte/index.d.ts +166 -0
  46. package/src/adapters/svelte/index.js +798 -0
  47. package/src/adapters/svelte.d.ts +3 -166
  48. package/src/adapters/svelte.js +3 -803
  49. package/src/adapters/vanilla/ai.js +30 -0
  50. package/src/adapters/vanilla/index.d.ts +179 -0
  51. package/src/adapters/vanilla/index.js +785 -0
  52. package/src/adapters/vanilla.d.ts +3 -179
  53. package/src/adapters/vanilla.js +3 -791
  54. package/src/adapters/vue/ai.js +52 -0
  55. package/src/adapters/vue/index.d.ts +299 -0
  56. package/src/adapters/vue/index.js +608 -0
  57. package/src/adapters/vue.d.ts +3 -299
  58. package/src/adapters/vue.js +3 -611
  59. package/src/ai/wu-ai-actions.js +261 -0
  60. package/src/ai/wu-ai-browser.js +663 -0
  61. package/src/ai/wu-ai-context.js +332 -0
  62. package/src/ai/wu-ai-conversation.js +554 -0
  63. package/src/ai/wu-ai-permissions.js +381 -0
  64. package/src/ai/wu-ai-provider.js +605 -0
  65. package/src/ai/wu-ai-schema.js +225 -0
  66. package/src/ai/wu-ai-triggers.js +396 -0
  67. package/src/ai/wu-ai.js +474 -0
  68. package/src/core/wu-app.js +50 -8
  69. package/src/core/wu-cache.js +1 -1
  70. package/src/core/wu-core.js +645 -677
  71. package/src/core/wu-html-parser.js +121 -211
  72. package/src/core/wu-iframe-sandbox.js +328 -0
  73. package/src/core/wu-mcp-bridge.js +647 -0
  74. package/src/core/wu-overrides.js +510 -0
  75. package/src/core/wu-prefetch.js +414 -0
  76. package/src/core/wu-proxy-sandbox.js +398 -75
  77. package/src/core/wu-sandbox.js +86 -268
  78. package/src/core/wu-script-executor.js +79 -182
  79. package/src/core/wu-snapshot-sandbox.js +149 -106
  80. package/src/core/wu-strategies.js +13 -0
  81. package/src/core/wu-style-bridge.js +0 -2
  82. package/src/index.js +139 -665
  83. package/dist/wu-framework.hex.js +0 -23
  84. package/dist/wu-framework.min.js +0 -1
  85. package/dist/wu-framework.obf.js +0 -1
  86. package/scripts/build-protected.js +0 -366
  87. package/scripts/build.js +0 -212
  88. package/scripts/rollup-plugin-hex.js +0 -143
  89. package/src/core/wu-registry.js +0 -60
  90. package/src/core/wu-sandbox-pool.js +0 -390
@@ -1,6 +1,6 @@
1
1
  /**
2
- * ๐Ÿš€ WU-FRAMEWORK: UNIVERSAL MICROFRONTENDS
3
- * Motor principal agnรณstico - Funciona con cualquier framework
2
+ * WU-FRAMEWORK: UNIVERSAL MICROFRONTENDS
3
+ * Motor principal agnostico - Funciona con cualquier framework
4
4
  */
5
5
 
6
6
  import { WuLoader } from './wu-loader.js';
@@ -16,8 +16,11 @@ import { WuPluginSystem } from './wu-plugin.js';
16
16
  import { WuLoadingStrategy } from './wu-strategies.js';
17
17
  import { WuErrorBoundary } from './wu-error-boundary.js';
18
18
  import { WuLifecycleHooks } from './wu-hooks.js';
19
- import { WuSandboxPool } from './wu-sandbox-pool.js';
20
- import { WuRegistry } from './wu-registry.js';
19
+ import { WuHtmlParser } from './wu-html-parser.js';
20
+ import { WuScriptExecutor } from './wu-script-executor.js';
21
+ import { WuIframeSandbox } from './wu-iframe-sandbox.js';
22
+ import { WuPrefetch } from './wu-prefetch.js';
23
+ import { WuOverrides } from './wu-overrides.js';
21
24
 
22
25
  export class WuCore {
23
26
  constructor(options = {}) {
@@ -26,6 +29,7 @@ export class WuCore {
26
29
  this.definitions = new Map(); // Definiciones de lifecycle
27
30
  this.manifests = new Map(); // Manifiestos cargados
28
31
  this.mounted = new Map(); // Apps montadas
32
+ this.hidden = new Map(); // Keep-alive hidden apps
29
33
 
30
34
  // Componentes core
31
35
  this.loader = new WuLoader();
@@ -33,451 +37,84 @@ export class WuCore {
33
37
  this.manifest = new WuManifest();
34
38
  this.store = store;
35
39
 
36
- // ๐Ÿš€ SISTEMAS ESENCIALES
40
+ // Strict sandbox support: HTML entry + script execution in proxy
41
+ this.htmlParser = new WuHtmlParser();
42
+ this.scriptExecutor = new WuScriptExecutor();
43
+
44
+ // Sistemas esenciales
37
45
  this.cache = new WuCache({ storage: 'localStorage', maxSize: 100 }); // 100MB cache
38
46
  this.eventBus = new WuEventBus();
39
47
  this.performance = new WuPerformance();
40
48
 
41
- // ๐ŸŽฏ ADVANCED SYSTEMS
49
+ // Advanced systems
42
50
  this.pluginSystem = new WuPluginSystem(this);
43
51
  this.strategies = new WuLoadingStrategy(this);
44
52
  this.errorBoundary = new WuErrorBoundary(this);
45
53
  this.hooks = new WuLifecycleHooks(this);
46
- this.sandboxPool = new WuSandboxPool(this);
47
- this.registry = new WuRegistry();
48
-
49
- // ๐Ÿ”„ HEALTH MONITORING CONFIG
50
- this.healthConfig = {
51
- enabled: options.healthMonitoring !== false, // Default: enabled
52
- autoHeal: options.autoHeal !== false, // Default: enabled
53
- agingThreshold: options.agingThreshold || Infinity, // Default: never refresh
54
- checkInterval: options.healthCheckInterval || 60000 // Default: 60s
55
- };
56
-
57
- // ๐Ÿ”„ HEALTH MONITORING STATE
58
- this.healthState = {
59
- monitor: null,
60
- healingInProgress: new Set(),
61
- systemStable: true,
62
- lastHealthCheck: Date.now(),
63
- mutationObserver: null,
64
- mutationCheckTimeout: null
65
- };
54
+ this.prefetcher = new WuPrefetch(this);
55
+ this.overrides = new WuOverrides();
66
56
 
67
57
  // Estado
68
58
  this.isInitialized = false;
69
59
 
70
- // ๐Ÿš€ Initialize self-healing system (ONLY if enabled)
71
- if (this.healthConfig.enabled) {
72
- this.initializeHealthMonitoring();
73
- logger.wuInfo('๐Ÿš€ Wu Framework initialized - Universal Microfrontends with Self-Healing');
74
- } else {
75
- logger.wuInfo('๐Ÿš€ Wu Framework initialized - Universal Microfrontends (Health Monitoring Disabled)');
76
- }
77
- }
78
-
79
- /**
80
- * ๐Ÿ”„ HEALTH MONITORING INITIALIZATION
81
- */
82
- initializeHealthMonitoring() {
83
- // ๐Ÿ’ซ Optimized health monitor with MutationObserver + reduced polling
84
- this.healthState.monitor = setInterval(() => {
85
- this.performHealthCheck();
86
- }, this.healthConfig.checkInterval);
87
-
88
- // ๐Ÿ” Smart MutationObserver for real-time DOM changes
89
- this.initializeMutationObserver();
90
-
91
- // ๐Ÿ›ก๏ธ Global error recovery (ONLY if autoHeal enabled)
92
- if (this.healthConfig.autoHeal) {
93
- window.addEventListener('error', (event) => {
94
- this.handleErrorEvent(event);
95
- });
96
-
97
- // ๐ŸŒŸ Unhandled promise rejection recovery
98
- window.addEventListener('unhandledrejection', (event) => {
99
- this.handleRejection(event);
100
- });
101
- }
102
-
103
- logger.wuInfo(`๐Ÿ”„ Health monitoring initialized - Interval: ${this.healthConfig.checkInterval}ms, AutoHeal: ${this.healthConfig.autoHeal}`);
104
- }
105
-
106
- /**
107
- * ๐Ÿ” SMART MUTATION OBSERVER: Observa SOLO los contenedores de apps montadas
108
- * FIX: Ya no observa todo document.body (memory leak)
109
- */
110
- initializeMutationObserver() {
111
- if (!window.MutationObserver) return;
112
-
113
- // Map para trackear observers por contenedor (evita memory leak)
114
- this.healthState.containerObservers = new Map();
115
-
116
- // Callback compartido para todos los observers
117
- this.healthState.mutationCallback = (mutations, observer) => {
118
- let affectedAppName = null;
119
-
120
- for (const mutation of mutations) {
121
- if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
122
- // Encontrar quรฉ app fue afectada
123
- for (const [appName, mounted] of this.mounted) {
124
- const container = mounted.hostContainer || mounted.container;
125
- if (mutation.target === container ||
126
- mutation.target.contains?.(container)) {
127
- affectedAppName = appName;
128
- break;
129
- }
130
- }
131
- }
132
- if (affectedAppName) break;
133
- }
134
-
135
- if (affectedAppName) {
136
- clearTimeout(this.healthState.mutationCheckTimeout);
137
- this.healthState.mutationCheckTimeout = setTimeout(() => {
138
- this.performHealthCheck();
139
- }, 1000);
140
- }
141
- };
142
-
143
- logger.wuDebug('๐Ÿ” MutationObserver system initialized (lazy per-container)');
144
- }
145
-
146
- /**
147
- * ๐Ÿ”— Observar contenedor especรญfico cuando se monta una app
148
- */
149
- observeContainer(appName, container) {
150
- if (!this.healthState.containerObservers || !container) return;
151
-
152
- // No observar si ya existe
153
- if (this.healthState.containerObservers.has(appName)) return;
154
-
155
- const observer = new MutationObserver(this.healthState.mutationCallback);
156
-
157
- // Observar solo el contenedor padre directo (no subtree profundo)
158
- const parentToObserve = container.parentElement || container;
159
- observer.observe(parentToObserve, {
160
- childList: true,
161
- subtree: false // โœ… Solo hijos directos, no todo el รกrbol
162
- });
163
-
164
- this.healthState.containerObservers.set(appName, observer);
165
- logger.wuDebug(`๐Ÿ” Observing container for ${appName}`);
166
- }
167
-
168
- /**
169
- * ๐Ÿ”“ Dejar de observar contenedor cuando se desmonta
170
- */
171
- unobserveContainer(appName) {
172
- if (!this.healthState.containerObservers) return;
173
-
174
- const observer = this.healthState.containerObservers.get(appName);
175
- if (observer) {
176
- observer.disconnect();
177
- this.healthState.containerObservers.delete(appName);
178
- logger.wuDebug(`๐Ÿ”“ Stopped observing container for ${appName}`);
179
- }
180
- }
181
-
182
- /**
183
- * ๐Ÿ’Š HEALTH CHECK: Continuous app monitoring
184
- */
185
- async performHealthCheck() {
186
- try {
187
- const now = Date.now();
188
- const healthIssues = [];
189
-
190
- // Check mounted apps vitality
191
- for (const [appName, mountedApp] of this.mounted) {
192
- const age = now - mountedApp.timestamp;
193
- const container = mountedApp.container;
194
-
195
- // ๐Ÿ›ก๏ธ ENHANCED CHECK: Verify container exists and is properly connected
196
- // IMPORTANT: Check multiple conditions to avoid false positives
197
- let isOrphaned = false;
198
-
199
- try {
200
- // Check 1: Direct DOM connection
201
- const containerInDOM = document.contains(container);
202
-
203
- // Check 2: Parent element connection (for shadow DOM)
204
- const parentInDOM = container.parentElement && document.contains(container.parentElement);
205
-
206
- // Check 3: Shadow root connection
207
- const shadowHost = mountedApp.sandbox?.shadowRoot?.host;
208
- const shadowHostInDOM = shadowHost && document.contains(shadowHost);
209
-
210
- // Check 4: Container still has valid properties (not detached)
211
- const hasValidParent = container.parentElement !== null || container.parentNode !== null;
212
-
213
- // Only mark as orphaned if ALL checks fail
214
- if (!containerInDOM && !parentInDOM && !shadowHostInDOM && !hasValidParent) {
215
- isOrphaned = true;
216
- }
217
- } catch (error) {
218
- // If checking fails, assume NOT orphaned (conservative approach)
219
- console.warn(`[Wu] โš ๏ธ Container check failed for ${appName}:`, error);
220
- isOrphaned = false;
221
- }
222
-
223
- if (isOrphaned) {
224
- healthIssues.push({
225
- type: 'orphaned_container',
226
- appName,
227
- severity: 'high'
228
- });
229
- }
230
-
231
- // Check app state - ONLY if it's truly unstable, not 'refreshed'
232
- // 'refreshed' is a normal state after aging refresh
233
- if (mountedApp.state && mountedApp.state !== 'stable' && mountedApp.state !== 'refreshed') {
234
- healthIssues.push({
235
- type: 'unstable_state',
236
- appName,
237
- severity: 'medium'
238
- });
239
- }
240
-
241
- // ๐Ÿ•ฐ๏ธ Check for long-running apps that might need refresh
242
- // ONLY if agingThreshold is configured (default: Infinity = never)
243
- if (age > this.healthConfig.agingThreshold) {
244
- healthIssues.push({
245
- type: 'aging_app',
246
- appName,
247
- severity: 'low',
248
- age: age
249
- });
250
- console.log(`[Wu] โฐ App ${appName} has been running for ${(age / 1000 / 60).toFixed(1)} minutes`);
251
- }
252
- }
253
-
254
- // Process health issues
255
- if (healthIssues.length > 0) {
256
- await this.processHealthIssues(healthIssues);
257
- }
258
-
259
- this.healthState.lastHealthCheck = now;
260
-
261
- } catch (error) {
262
- console.warn('[Wu] โš ๏ธ Health check failed:', error);
263
- }
264
- }
265
-
266
- /**
267
- * ๐Ÿ› ๏ธ HEALTH ISSUE PROCESSOR
268
- */
269
- async processHealthIssues(issues) {
270
- for (const issue of issues) {
271
- if (this.healthState.healingInProgress.has(issue.appName)) {
272
- continue; // Already healing
273
- }
274
-
275
- console.log(`[Wu] ๐Ÿฉบ Health issue detected:`, issue);
276
-
277
- switch (issue.type) {
278
- case 'orphaned_container':
279
- await this.healOrphanedContainer(issue.appName);
280
- break;
281
- case 'unstable_state':
282
- await this.stabilizeAppState(issue.appName);
283
- break;
284
- case 'aging_app':
285
- await this.refreshAgingApp(issue.appName);
286
- break;
287
- }
288
- }
289
- }
290
-
291
- /**
292
- * ๐Ÿ”„ ORPHANED CONTAINER HEALING
293
- */
294
- async healOrphanedContainer(appName) {
295
- this.healthState.healingInProgress.add(appName);
296
-
297
- try {
298
- console.log(`[Wu] ๐Ÿ”„ Healing orphaned container for ${appName}`);
299
-
300
- // Clean up orphaned state
301
- await this.unmount(appName);
302
-
303
- // ๐Ÿ” Find a suitable container to remount
304
- const suitableContainers = [
305
- document.querySelector(`[data-wu-app="${appName}"]`),
306
- document.querySelector(`#${appName}-container`),
307
- document.querySelector(`.${appName}-container`),
308
- document.querySelector(`[id*="${appName}"]`)
309
- ].filter(Boolean);
310
-
311
- if (suitableContainers.length > 0) {
312
- const container = suitableContainers[0];
313
- const containerSelector = container.id ? `#${container.id}` : `.${container.className.split(' ')[0]}`;
314
-
315
- // ๐ŸŽฏ Only attempt re-mount if selector is valid
316
- if (containerSelector && containerSelector !== '#' && containerSelector !== '.') {
317
- await this.mount(appName, containerSelector);
318
- console.log(`[Wu] โœจ Successfully healed orphaned ${appName} in ${containerSelector}`);
319
- } else {
320
- console.warn(`[Wu] โš ๏ธ Could not determine valid selector for ${appName}`);
321
- }
322
- } else {
323
- console.warn(`[Wu] โš ๏ธ No suitable container found for healing ${appName}`);
324
- }
325
-
326
- } catch (error) {
327
- console.warn(`[Wu] โš ๏ธ Failed to heal orphaned ${appName}:`, error);
328
- } finally {
329
- this.healthState.healingInProgress.delete(appName);
330
- }
331
- }
332
-
333
- /**
334
- * โš–๏ธ APP STATE STABILIZATION
335
- */
336
- async stabilizeAppState(appName) {
337
- this.healthState.healingInProgress.add(appName);
338
-
339
- try {
340
- console.log(`[Wu] โš–๏ธ Stabilizing app state for ${appName}`);
341
-
342
- const mounted = this.mounted.get(appName);
343
- if (mounted) {
344
- mounted.state = 'stable';
345
- // โš ๏ธ DO NOT RESET TIMESTAMP - preserves app age for aging checks
346
- console.log(`[Wu] โœจ App state stabilized for ${appName}`);
347
- }
348
-
349
- } catch (error) {
350
- console.warn(`[Wu] โš ๏ธ Failed to stabilize ${appName}:`, error);
351
- } finally {
352
- this.healthState.healingInProgress.delete(appName);
353
- }
60
+ logger.wuInfo('Wu Framework initialized - Universal Microfrontends');
354
61
  }
355
62
 
356
63
  /**
357
- * ๐Ÿ•ฐ๏ธ AGING APP REFRESH
358
- * NOTE: This should ONLY trigger if agingThreshold is explicitly configured
359
- */
360
- async refreshAgingApp(appName) {
361
- // Safety check: Don't refresh if aging is disabled (Infinity)
362
- if (this.healthConfig.agingThreshold === Infinity) {
363
- console.warn(`[Wu] โš ๏ธ refreshAgingApp called but agingThreshold is Infinity - skipping`);
364
- return;
365
- }
366
-
367
- this.healthState.healingInProgress.add(appName);
368
-
369
- try {
370
- console.log(`[Wu] ๐Ÿ•ฐ๏ธ Refreshing aging app for ${appName}`);
371
-
372
- const mounted = this.mounted.get(appName);
373
- if (mounted) {
374
- // Gentle refresh without full remount
375
- // Reset timestamp ONLY when explicitly configured aging refresh
376
- mounted.timestamp = Date.now();
377
- mounted.state = 'refreshed';
378
-
379
- // Trigger a soft refresh of the app if it supports it
380
- if (mounted.lifecycle.refresh) {
381
- await mounted.lifecycle.refresh(mounted.container);
382
- }
383
-
384
- console.log(`[Wu] โœจ App refreshed for ${appName}`);
385
- }
386
-
387
- } catch (error) {
388
- console.warn(`[Wu] โš ๏ธ Failed to refresh ${appName}:`, error);
389
- } finally {
390
- this.healthState.healingInProgress.delete(appName);
391
- }
392
- }
393
-
394
- /**
395
- * ๐Ÿ’ฅ ERROR EVENT HANDLER
396
- */
397
- handleErrorEvent(event) {
398
- console.log('[Wu] ๐Ÿ’ฅ Error event detected:', event.error);
399
-
400
- // Check if error is related to any mounted apps
401
- for (const [appName, mounted] of this.mounted) {
402
- if (event.filename && event.filename.includes(mounted.app.url)) {
403
- console.log(`[Wu] ๐ŸŽฏ Error traced to ${appName}, initiating recovery...`);
404
-
405
- // Schedule healing
406
- setTimeout(() => {
407
- this.healOrphanedContainer(appName);
408
- }, 1000);
409
-
410
- break;
411
- }
412
- }
413
- }
414
-
415
- /**
416
- * ๐Ÿšซ REJECTION HANDLER
417
- */
418
- handleRejection(event) {
419
- console.log('[Wu] ๐Ÿšซ Promise rejection detected:', event.reason);
420
-
421
- // Prevent default unhandled rejection
422
- event.preventDefault();
423
-
424
- // Mark system as unstable temporarily
425
- this.healthState.systemStable = false;
426
-
427
- // Schedule stability restoration
428
- setTimeout(() => {
429
- this.healthState.systemStable = true;
430
- console.log('[Wu] โœจ System stability restored');
431
- }, 5000);
432
- }
433
-
434
- /**
435
- * Inicializar wu-framework con configuraciรณn de apps
436
- * @param {Object} config - Configuraciรณn { apps: [{name, url}, ...] }
64
+ * Inicializar wu-framework con configuracion de apps
65
+ * @param {Object} config - Configuracion { apps: [{name, url}, ...] }
437
66
  */
438
67
  async init(config) {
439
68
  if (this.isInitialized) {
440
- console.warn('[Wu] Framework already initialized');
69
+ logger.wuWarn('Framework already initialized');
441
70
  return;
442
71
  }
443
72
 
444
- logger.wuDebug('๐Ÿ”ง Initializing with apps:', config.apps?.map(app => app.name));
73
+ // Global sandbox mode: 'module' (default) or 'strict'
74
+ this._sandboxMode = config.sandbox || 'module';
75
+
76
+ logger.wuDebug(`Initializing (sandbox: ${this._sandboxMode}) with apps:`, config.apps?.map(app => app.name));
445
77
 
446
78
  try {
447
- // ๐Ÿช Execute beforeInit hooks
79
+ // Execute beforeInit hooks
448
80
  const beforeInitResult = await this.hooks.execute('beforeInit', { config });
449
81
  if (beforeInitResult.cancelled) {
450
- console.warn('[Wu] Initialization cancelled by beforeInit hook');
82
+ logger.wuWarn('Initialization cancelled by beforeInit hook');
451
83
  return;
452
84
  }
453
85
 
454
- // ๐Ÿ”Œ Call plugin beforeInit hooks
86
+ // Call plugin beforeInit hooks
455
87
  await this.pluginSystem.callHook('beforeInit', { config });
456
88
 
457
- // ๐ŸŠ Initialize sandbox pool
458
- await this.sandboxPool.init();
89
+ // Configure and apply cookie overrides (QA/testing: wu-override:<app>=<url>)
90
+ if (config.overrides) {
91
+ this.overrides.configure(config.overrides);
92
+ }
93
+ const apps = config.apps || [];
94
+ this.overrides.refresh();
95
+ this.overrides.applyToApps(apps);
459
96
 
460
97
  // Registrar todas las apps
461
- for (const appConfig of config.apps || []) {
98
+ for (const appConfig of apps) {
462
99
  await this.registerApp(appConfig);
463
100
  }
464
101
 
465
- // ๐ŸŽฏ Preload apps with eager/preload strategies
102
+ // Preload apps with eager/preload strategies
466
103
  await this.strategies.preload(config.apps || []);
467
104
 
468
105
  this.isInitialized = true;
469
106
 
470
- // ๐Ÿช Execute afterInit hooks
107
+ // Execute afterInit hooks
471
108
  await this.hooks.execute('afterInit', { config });
472
109
 
473
- // ๐Ÿ”Œ Call plugin afterInit hooks
110
+ // Call plugin afterInit hooks
474
111
  await this.pluginSystem.callHook('afterInit', { config });
475
112
 
476
- logger.wuInfo('โœ… Framework initialized successfully');
113
+ logger.wuInfo('Framework initialized successfully');
477
114
  } catch (error) {
478
- console.error('[Wu] โŒ Initialization failed:', error);
115
+ logger.wuError('Initialization failed:', error);
479
116
 
480
- // ๐Ÿ”Œ Call plugin error hooks
117
+ // Call plugin error hooks
481
118
  await this.pluginSystem.callHook('onError', { phase: 'init', error });
482
119
 
483
120
  throw error;
@@ -485,14 +122,14 @@ export class WuCore {
485
122
  }
486
123
 
487
124
  /**
488
- * Registrar una aplicaciรณn
125
+ * Registrar una aplicacion
489
126
  * @param {Object} appConfig - { name, url }
490
127
  */
491
128
  async registerApp(appConfig) {
492
129
  const { name, url } = appConfig;
493
130
 
494
131
  try {
495
- logger.wuDebug(`๐Ÿ“ฆ Registering app: ${name} from ${url}`);
132
+ logger.wuDebug(`Registering app: ${name} from ${url}`);
496
133
 
497
134
  // Cargar manifest
498
135
  const manifestData = await this.manifest.load(url);
@@ -506,9 +143,9 @@ export class WuCore {
506
143
  status: 'registered'
507
144
  });
508
145
 
509
- logger.wuDebug(`โœ… App ${name} registered successfully`);
146
+ logger.wuDebug(`App ${name} registered successfully`);
510
147
  } catch (error) {
511
- console.error(`[Wu] โŒ Failed to register app ${name}:`, error);
148
+ logger.wuError(`Failed to register app ${name}:`, error);
512
149
  throw error;
513
150
  }
514
151
  }
@@ -525,79 +162,86 @@ export class WuCore {
525
162
 
526
163
  this.definitions.set(appName, lifecycle);
527
164
 
528
- // ๐Ÿ”” Notify registry that app is ready
529
- this.registry.markAsRegistered(appName);
530
-
531
- // ๐Ÿ“ก Dispatch custom event for external listeners
165
+ // Dispatch custom event for external listeners
532
166
  const event = new CustomEvent('wu:app:ready', {
533
167
  detail: { appName, timestamp: Date.now() }
534
168
  });
535
169
  window.dispatchEvent(event);
536
170
 
537
- logger.wuDebug(`๐Ÿ“‹ Lifecycle defined for: ${appName}`);
171
+ logger.wuDebug(`Lifecycle defined for: ${appName}`);
538
172
  }
539
173
 
540
174
  /**
541
- * ๐Ÿš€ MOUNT APP: Multi-retry app mounting with self-healing
175
+ * Mount app with multi-retry mounting and recovery.
176
+ * If the app is in keep-alive (hidden) state, shows it instantly.
177
+ *
542
178
  * @param {string} appName - Nombre de la app
543
179
  * @param {string} containerSelector - Selector del contenedor
544
180
  */
545
181
  async mount(appName, containerSelector) {
182
+ // Check if app is in keep-alive (hidden) state
183
+ const hiddenEntry = this.hidden.get(appName);
184
+ if (hiddenEntry) {
185
+ if (hiddenEntry.containerSelector === containerSelector) {
186
+ // Same container โ†’ instant show (no reload)
187
+ return await this.show(appName);
188
+ }
189
+ // Different container โ†’ destroy hidden state, remount normally
190
+ await this._destroyHidden(appName);
191
+ }
192
+
546
193
  return await this.mountWithRecovery(appName, containerSelector, 0);
547
194
  }
548
195
 
549
196
  /**
550
- * ๐Ÿ”„ MOUNT WITH RECOVERY: Self-healing app mounting
197
+ * Mount with recovery: self-healing app mounting
551
198
  */
552
199
  async mountWithRecovery(appName, containerSelector, attempt = 0) {
553
200
  const maxAttempts = 3;
554
201
 
555
202
  try {
556
- // โšก Start performance measurement
203
+ // Start performance measurement
557
204
  this.performance.startMeasure('mount', appName);
558
205
 
559
- logger.wuDebug(`๐Ÿ”— Mounting ${appName} in ${containerSelector} (attempt ${attempt + 1})`);
206
+ logger.wuDebug(`Mounting ${appName} in ${containerSelector} (attempt ${attempt + 1})`);
560
207
 
561
- // ๐Ÿช Execute beforeLoad hooks
208
+ // Execute beforeLoad hooks
562
209
  const beforeLoadResult = await this.hooks.execute('beforeLoad', { appName, containerSelector, attempt });
563
210
  if (beforeLoadResult.cancelled) {
564
- console.warn('[Wu] Mount cancelled by beforeLoad hook');
211
+ logger.wuWarn('Mount cancelled by beforeLoad hook');
565
212
  return;
566
213
  }
567
214
 
568
- // ๐Ÿ”Œ Call plugin beforeMount hooks
215
+ // Call plugin beforeMount hooks
569
216
  const pluginBeforeMount = await this.pluginSystem.callHook('beforeMount', { appName, containerSelector });
570
217
  if (pluginBeforeMount === false) {
571
- console.warn('[Wu] Mount cancelled by plugin beforeMount hook');
218
+ logger.wuWarn('Mount cancelled by plugin beforeMount hook');
572
219
  return;
573
220
  }
574
221
 
575
- // ๐Ÿ”ฎ Quantum state verification
222
+ // Verify app is registered
576
223
  const app = this.apps.get(appName);
577
224
  if (!app) {
578
225
  throw new Error(`App ${appName} not registered. Call wu.init() first.`);
579
226
  }
580
227
 
581
- // ๐ŸŒŸ Container reality check
228
+ // Container reality check
582
229
  const container = document.querySelector(containerSelector);
583
230
  if (!container) {
584
231
  throw new Error(`Container not found: ${containerSelector}`);
585
232
  }
586
233
 
587
- // ๐ŸŠ Acquire sandbox from pool (if configured)
588
- const poolSandbox = this.sandboxPool.acquire(appName);
589
-
590
- // ๐Ÿ›ก๏ธ Quantum sandbox creation - pasar manifest con styleMode y URL de la app
234
+ // Create sandbox - pasar manifest con styleMode y URL de la app
591
235
  const sandbox = this.sandbox.create(appName, container, {
592
236
  manifest: app.manifest,
593
237
  styleMode: app.manifest?.styleMode,
594
238
  appUrl: app.url // Pasar URL de la app para filtrar estilos de apps fully-isolated
595
239
  });
596
240
 
597
- // ๐Ÿช Execute afterLoad hooks
241
+ // Execute afterLoad hooks
598
242
  await this.hooks.execute('afterLoad', { appName, containerSelector, sandbox });
599
243
 
600
- // ๐Ÿš€ Transcendent lifecycle resolution
244
+ // Resolve lifecycle definition
601
245
  let lifecycle = this.definitions.get(appName);
602
246
  if (!lifecycle) {
603
247
  // Load remote app
@@ -609,56 +253,53 @@ export class WuCore {
609
253
  }
610
254
  }
611
255
 
612
- // ๐Ÿช Execute beforeMount hooks
256
+ // Execute beforeMount hooks
613
257
  const beforeMountResult = await this.hooks.execute('beforeMount', { appName, containerSelector, sandbox, lifecycle });
614
258
  if (beforeMountResult.cancelled) {
615
- console.warn('[Wu] Mount cancelled by beforeMount hook');
259
+ logger.wuWarn('Mount cancelled by beforeMount hook');
616
260
  return;
617
261
  }
618
262
 
619
- // ๐ŸŽจ Wait for styles to be ready before mounting
263
+ // Wait for styles to be ready before mounting
620
264
  if (sandbox.stylesReady) {
621
- console.log(`[Wu] โณ Waiting for styles to be ready for ${appName}...`);
265
+ logger.wuDebug(`Waiting for styles to be ready for ${appName}...`);
622
266
  await sandbox.stylesReady;
623
- console.log(`[Wu] โœ… Styles ready for ${appName}`);
267
+ logger.wuDebug(`Styles ready for ${appName}`);
624
268
  }
625
269
 
626
- // โšก Quantum mounting execution
270
+ // Execute mount lifecycle
627
271
  await lifecycle.mount(sandbox.container);
628
272
 
629
- // ๐ŸŒŒ Registration in mounted dimension
273
+ // Register mounted app
630
274
  this.mounted.set(appName, {
631
275
  app,
632
276
  sandbox,
633
- poolSandbox,
634
277
  lifecycle,
635
278
  container: sandbox.container,
636
279
  hostContainer: container,
280
+ containerSelector,
637
281
  timestamp: Date.now(),
638
282
  state: 'stable'
639
283
  });
640
284
 
641
- // ๐Ÿ” Observar contenedor para health monitoring (sin memory leak)
642
- this.observeContainer(appName, container);
643
-
644
- // โšก End performance measurement
285
+ // End performance measurement
645
286
  const mountTime = this.performance.endMeasure('mount', appName);
646
287
 
647
- // ๐Ÿช Execute afterMount hooks
288
+ // Execute afterMount hooks
648
289
  await this.hooks.execute('afterMount', { appName, containerSelector, sandbox, mountTime });
649
290
 
650
- // ๐Ÿ”Œ Call plugin afterMount hooks
291
+ // Call plugin afterMount hooks
651
292
  await this.pluginSystem.callHook('afterMount', { appName, containerSelector, mountTime });
652
293
 
653
- // ๐Ÿ“ข Emit mount event
294
+ // Emit mount event
654
295
  this.eventBus.emit('app:mounted', { appName, mountTime, attempt }, { appName });
655
296
 
656
- logger.wuInfo(`โœ… ${appName} mounted successfully in ${mountTime.toFixed(2)}ms`);
297
+ logger.wuInfo(`${appName} mounted successfully in ${mountTime.toFixed(2)}ms`);
657
298
 
658
299
  } catch (error) {
659
- console.error(`[Wu] โŒ Mount attempt ${attempt + 1} failed for ${appName}:`, error);
300
+ logger.wuError(`Mount attempt ${attempt + 1} failed for ${appName}:`, error);
660
301
 
661
- // ๐Ÿ›ก๏ธ Use error boundary for intelligent error handling
302
+ // Use error boundary for intelligent error handling
662
303
  const errorResult = await this.errorBoundary.handle(error, {
663
304
  appName,
664
305
  containerSelector,
@@ -666,54 +307,63 @@ export class WuCore {
666
307
  container: containerSelector
667
308
  });
668
309
 
669
- // Si el error boundary recuperรณ el error, no necesitamos reintentar
310
+ // Si el error boundary recupero el error, no necesitamos reintentar
670
311
  if (errorResult.recovered) {
671
- console.log(`[Wu] โœจ Error recovered by error boundary`);
312
+ logger.wuDebug('Error recovered by error boundary');
672
313
  return;
673
314
  }
674
315
 
675
- // ๐Ÿ”„ RECOVERY PROTOCOL
316
+ // Recovery protocol
676
317
  if (attempt < maxAttempts - 1 && errorResult.action === 'retry') {
677
- console.log(`[Wu] ๐ŸŒŸ Initiating recovery protocol...`);
318
+ logger.wuDebug('Initiating recovery protocol...');
678
319
 
679
- // ๐Ÿ› ๏ธ Clean app state
320
+ // Clean app state
680
321
  await this.appStateCleanup(appName, containerSelector);
681
322
 
682
- // โฑ๏ธ Temporal stabilization
323
+ // Temporal stabilization
683
324
  await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
684
325
 
685
- // ๐Ÿš€ Recursive mounting with recovery
326
+ // Recursive mounting with recovery
686
327
  return await this.mountWithRecovery(appName, containerSelector, attempt + 1);
687
328
  }
688
329
 
689
- // ๐Ÿ”Œ Call plugin error hooks
330
+ // Call plugin error hooks
690
331
  await this.pluginSystem.callHook('onError', { phase: 'mount', error, appName });
691
332
 
692
- // ๐Ÿ’ฅ Final mount failure - error boundary already handled fallback UI
333
+ // Final mount failure - error boundary already handled fallback UI
693
334
  throw error;
694
335
  }
695
336
  }
696
337
 
697
338
  /**
698
- * ๐Ÿ› ๏ธ APP STATE CLEANUP: Enhanced container cleanup with framework protection
339
+ * App state cleanup: Enhanced container cleanup with framework protection
699
340
  */
700
341
  async appStateCleanup(appName, containerSelector) {
701
342
  try {
702
- console.log(`[Wu] ๐Ÿงน Starting app state cleanup for ${appName}...`);
343
+ logger.wuDebug(`Starting app state cleanup for ${appName}...`);
344
+
345
+ // Clear hidden (keep-alive) state if present
346
+ if (this.hidden.has(appName)) {
347
+ try {
348
+ await this._destroyHidden(appName);
349
+ } catch (hiddenError) {
350
+ logger.wuWarn('Hidden app cleanup failed:', hiddenError);
351
+ }
352
+ }
703
353
 
704
- // ๐Ÿš€ Clear any existing mounted state safely
354
+ // Clear any existing mounted state safely
705
355
  if (this.mounted.has(appName)) {
706
356
  try {
707
- await this.unmount(appName);
357
+ await this.unmount(appName, { force: true });
708
358
  } catch (unmountError) {
709
- console.warn(`[Wu] โš ๏ธ Unmount failed during cleanup:`, unmountError);
359
+ logger.wuWarn('Unmount failed during cleanup:', unmountError);
710
360
  }
711
361
  }
712
362
 
713
- // ๐ŸŒŒ Enhanced container cleanup with Vue safety measures
363
+ // Enhanced container cleanup with Vue safety measures
714
364
  const container = document.querySelector(containerSelector);
715
365
  if (container) {
716
- // ๐Ÿ›ก๏ธ Protect Vue's reactivity system
366
+ // Protect Vue's reactivity system
717
367
  if (container.shadowRoot) {
718
368
  try {
719
369
  // Clear shadow root content safely
@@ -722,43 +372,43 @@ export class WuCore {
722
372
  try {
723
373
  child.remove();
724
374
  } catch (removeError) {
725
- console.warn(`[Wu] โš ๏ธ Failed to remove shadow child:`, removeError);
375
+ logger.wuWarn('Failed to remove shadow child:', removeError);
726
376
  }
727
377
  });
728
378
  } catch (shadowError) {
729
- console.warn(`[Wu] โš ๏ธ Shadow root cleanup failed:`, shadowError);
379
+ logger.wuWarn('Shadow root cleanup failed:', shadowError);
730
380
  }
731
381
  }
732
382
 
733
- // ๐Ÿ”ฎ Clear any direct children if no shadow root
383
+ // Clear any direct children if no shadow root
734
384
  if (!container.shadowRoot && container.children.length > 0) {
735
385
  try {
736
386
  container.innerHTML = '';
737
387
  } catch (htmlError) {
738
- console.warn(`[Wu] โš ๏ธ Container innerHTML cleanup failed:`, htmlError);
388
+ logger.wuWarn('Container innerHTML cleanup failed:', htmlError);
739
389
  }
740
390
  }
741
391
 
742
- // ๐ŸŒŸ Reset container attributes
392
+ // Reset container attributes
743
393
  container.removeAttribute('data-wu-app');
744
394
  container.removeAttribute('data-quantum-state');
745
395
  container.removeAttribute('wu-debug');
746
396
  }
747
397
 
748
- // ๐ŸŽฏ Reset definition state
398
+ // Reset definition state
749
399
  this.definitions.delete(appName);
750
400
 
751
- // ๐Ÿš€ Clear sandbox registry
401
+ // Clear sandbox registry
752
402
  if (this.sandbox && this.sandbox.sandboxes) {
753
403
  this.sandbox.sandboxes.delete(appName);
754
404
  }
755
405
 
756
- console.log(`[Wu] โœ… App state cleaned successfully for ${appName}`);
406
+ logger.wuDebug(`App state cleaned successfully for ${appName}`);
757
407
 
758
408
  } catch (cleanupError) {
759
- console.warn(`[Wu] โš ๏ธ App cleanup partial failure for ${appName}:`, cleanupError);
409
+ logger.wuWarn(`App cleanup partial failure for ${appName}:`, cleanupError);
760
410
 
761
- // ๐ŸŒŒ Emergency cleanup - force clear everything
411
+ // Emergency cleanup - force clear everything
762
412
  try {
763
413
  const container = document.querySelector(containerSelector);
764
414
  if (container) {
@@ -770,163 +420,239 @@ export class WuCore {
770
420
  }, 100);
771
421
  }
772
422
  } catch (emergencyError) {
773
- console.error(`[Wu] ๐Ÿ’ฅ Emergency cleanup failed:`, emergencyError);
423
+ logger.wuError('Emergency cleanup failed:', emergencyError);
774
424
  }
775
425
  }
776
426
  }
777
427
 
778
428
  /**
779
- * ๐Ÿ’ฅ ERROR STATE RENDERER: Visual error manifestation
429
+ * Remote app loader: Load app in the configured sandbox mode.
430
+ *
431
+ * Three modes:
432
+ *
433
+ * - module (default): ES6 import() + patchWindow for side-effect tracking.
434
+ * Works with Vite, HMR, ES modules. App code runs in global scope.
435
+ * Proxy is a cleanup tracker, not an isolation boundary.
436
+ *
437
+ * - strict: Hidden iframe + real import(). True JS isolation.
438
+ * App code runs in iframe's window (separate global context).
439
+ * Document operations proxied to Shadow DOM.
440
+ * Preserves: tree shaking, source maps, HMR.
441
+ * Falls back to eval mode if import() fails (CORS, etc.)
442
+ *
443
+ * - eval: Fetch HTML โ†’ parse scripts โ†’ execute with(proxy).
444
+ * Maximum JS isolation via with(proxy) statement.
445
+ * Requires bundled apps (UMD/IIFE), not ES modules.
446
+ * No tree shaking, no source maps, no HMR.
447
+ *
448
+ * Set per-app: { name: 'app', url: '...', sandbox: 'strict' }
449
+ * Or globally: wu.init({ sandbox: 'strict', apps: [...] })
780
450
  */
781
- async renderErrorState(containerSelector, appName, error) {
451
+ async loadAndMountRemoteApp(app, sandbox) {
452
+ const mode = app.sandbox || this._sandboxMode || 'module';
453
+
454
+ if (mode === 'strict') {
455
+ await this._loadStrict(app, sandbox);
456
+ } else if (mode === 'eval') {
457
+ await this._loadEval(app, sandbox);
458
+ } else {
459
+ await this._loadModule(app, sandbox);
460
+ }
461
+ }
462
+
463
+ /**
464
+ * MODULE MODE: import() + patchWindow (default).
465
+ * Side effects tracked during load, cleaned on unmount.
466
+ * App code runs in global scope.
467
+ */
468
+ async _loadModule(app, sandbox) {
469
+ const moduleUrl = await this.resolveModulePath(app);
470
+ logger.wuDebug(`[module] Loading ES module: ${moduleUrl}`);
471
+
472
+ const jsSandbox = sandbox.jsSandbox;
473
+ if (jsSandbox?.patchWindow) {
474
+ jsSandbox.patchWindow();
475
+ }
476
+
782
477
  try {
783
- const container = document.querySelector(containerSelector);
784
- if (!container) return;
785
-
786
- // ๐Ÿ›ก๏ธ Clear container safely
787
- container.innerHTML = '';
788
-
789
- // ๐ŸŒŒ Create quantum error visualization with safe DOM manipulation
790
- const errorContainer = document.createElement('div');
791
- errorContainer.className = 'quantum-error-state';
792
-
793
- // Apply styles programmatically
794
- Object.assign(errorContainer.style, {
795
- padding: '2rem',
796
- borderRadius: '12px',
797
- background: 'linear-gradient(135deg, #1a1a2e, #16213e)',
798
- border: '2px solid #ff6b6b',
799
- color: '#fff',
800
- fontFamily: '"Courier New", monospace',
801
- textAlign: 'center',
802
- boxShadow: '0 0 20px rgba(255, 107, 107, 0.3)'
803
- });
478
+ await this.moduleLoader(moduleUrl, app.name);
479
+ logger.wuDebug(`[module] ES module loaded: ${app.name}`);
480
+ } catch (error) {
481
+ logger.wuError(`[module] Failed to load ${moduleUrl}:`, error);
482
+ throw error;
483
+ } finally {
484
+ if (jsSandbox?.unpatchWindow) {
485
+ jsSandbox.unpatchWindow();
486
+ }
487
+ }
488
+ }
804
489
 
805
- // Create elements safely
806
- const icon = document.createElement('div');
807
- icon.textContent = '๐ŸŒŒ';
808
- Object.assign(icon.style, {
809
- fontSize: '3rem',
810
- marginBottom: '1rem'
811
- });
490
+ /**
491
+ * STRICT MODE: Hidden iframe + real import().
492
+ *
493
+ * The iframe provides a separate window context. import() inside the iframe
494
+ * is a real ES module import โ€” tree shaking, source maps, and HMR all work.
495
+ *
496
+ * Pipeline:
497
+ * 1. Create hidden iframe with <base href="appUrl">
498
+ * 2. Patch iframe's document โ†’ DOM operations go to Shadow DOM
499
+ * 3. import() the app module inside iframe
500
+ * 4. Wait for wu.define() registration
501
+ *
502
+ * If import() fails (CORS, network, etc.), falls back to eval mode
503
+ * with a console warning explaining why.
504
+ */
505
+ async _loadStrict(app, sandbox) {
506
+ logger.wuDebug(`[strict] Loading ${app.name} via iframe sandbox`);
812
507
 
813
- const title = document.createElement('h3');
814
- title.textContent = 'MOUNT ERROR DETECTED';
815
- Object.assign(title.style, {
816
- color: '#ff6b6b',
817
- margin: '0 0 1rem 0'
818
- });
508
+ // Create and activate iframe sandbox
509
+ const iframeSandbox = new WuIframeSandbox(app.name);
510
+ iframeSandbox.activate(app.url, sandbox.container, sandbox.shadowRoot);
511
+ sandbox.iframeSandbox = iframeSandbox;
819
512
 
820
- const description = document.createElement('p');
821
- description.textContent = `App "${appName}" failed to mount in the container`;
822
- Object.assign(description.style, {
823
- margin: '0 0 1rem 0',
824
- opacity: '0.8'
825
- });
513
+ try {
514
+ // Resolve module path (same logic as module mode)
515
+ const moduleUrl = await this.resolveModulePath(app);
516
+ logger.wuDebug(`[strict] Importing module in iframe: ${moduleUrl}`);
517
+
518
+ // Import module inside iframe โ€” real import()!
519
+ await iframeSandbox.importModule(moduleUrl);
520
+ logger.wuDebug(`[strict] Module imported for ${app.name}`);
521
+
522
+ } catch (importError) {
523
+ // import() failed โ€” likely CORS or module error.
524
+ // Fall back to eval mode (fetch + parse + with(proxy)).
525
+ logger.wuWarn(
526
+ `[strict] iframe import failed for ${app.name}: ${importError.message}\n` +
527
+ `Falling back to eval mode (fetch + parse + execute with proxy).\n` +
528
+ `To fix: ensure the app's dev server sets Access-Control-Allow-Origin: * headers,\n` +
529
+ `or use sandbox: 'eval' explicitly for UMD/IIFE bundles.`
530
+ );
531
+
532
+ // Destroy failed iframe
533
+ iframeSandbox.destroy();
534
+ sandbox.iframeSandbox = null;
535
+
536
+ // Fallback to eval mode
537
+ await this._loadEval(app, sandbox);
538
+ return;
539
+ }
826
540
 
827
- const details = document.createElement('details');
828
- Object.assign(details.style, {
829
- margin: '1rem 0',
830
- textAlign: 'left'
831
- });
541
+ // Wait for wu.define()
542
+ await this._waitForDefine(app.name, 'strict');
832
543
 
833
- const summary = document.createElement('summary');
834
- summary.textContent = '๐Ÿ” Debug Info';
835
- Object.assign(summary.style, {
836
- cursor: 'pointer',
837
- color: '#4ecdc4'
838
- });
544
+ logger.wuDebug(`[strict] ${app.name} loaded and registered via iframe`);
545
+ }
839
546
 
840
- const pre = document.createElement('pre');
841
- pre.textContent = error.message; // Safe text content, no HTML injection
842
- Object.assign(pre.style, {
843
- background: 'rgba(0,0,0,0.3)',
844
- padding: '1rem',
845
- borderRadius: '6px',
846
- marginTop: '0.5rem',
847
- overflow: 'auto',
848
- fontSize: '0.8rem'
849
- });
547
+ /**
548
+ * EVAL MODE: Fetch HTML โ†’ parse โ†’ execute scripts inside proxy.
549
+ *
550
+ * Maximum JS isolation via with(proxy) statement โ€” all unqualified
551
+ * identifiers (setTimeout, document, fetch) go through proxy traps.
552
+ *
553
+ * Requires bundled apps (UMD/IIFE). ES modules cannot be eval'd.
554
+ * No tree shaking, no source maps, no HMR.
555
+ *
556
+ * Pipeline:
557
+ * 1. Fetch HTML from app URL
558
+ * 2. Parse: extract scripts (inline + external), styles, clean DOM
559
+ * 3. Inject DOM + styles into Shadow DOM
560
+ * 4. Execute all scripts inside the proxy via WuScriptExecutor
561
+ * 5. Wait for wu.define()
562
+ */
563
+ async _loadEval(app, sandbox) {
564
+ logger.wuDebug(`[eval] Loading ${app.name} from ${app.url}`);
850
565
 
851
- const button = document.createElement('button');
852
- button.textContent = '๐Ÿ”„ Reload Page';
853
- Object.assign(button.style, {
854
- background: 'linear-gradient(45deg, #4ecdc4, #44a08d)',
855
- border: 'none',
856
- padding: '0.8rem 1.5rem',
857
- borderRadius: '6px',
858
- color: 'white',
859
- cursor: 'pointer',
860
- fontWeight: 'bold',
861
- transition: 'transform 0.2s'
862
- });
566
+ const jsSandbox = sandbox.jsSandbox;
567
+ const proxy = jsSandbox.getProxy();
863
568
 
864
- // Add safe event listeners
865
- button.addEventListener('click', () => window.location.reload());
866
- button.addEventListener('mouseenter', () => button.style.transform = 'scale(1.05)');
867
- button.addEventListener('mouseleave', () => button.style.transform = 'scale(1)');
569
+ if (!proxy) {
570
+ throw new Error(`[eval] No active proxy for ${app.name}. Sandbox must be activated first.`);
571
+ }
868
572
 
869
- // Assemble DOM structure
870
- details.appendChild(summary);
871
- details.appendChild(pre);
573
+ // 1. Fetch and parse HTML
574
+ const parsed = await this.htmlParser.fetchAndParse(app.url, app.name);
872
575
 
873
- errorContainer.appendChild(icon);
874
- errorContainer.appendChild(title);
875
- errorContainer.appendChild(description);
876
- errorContainer.appendChild(details);
877
- errorContainer.appendChild(button);
576
+ // 2. Inject clean DOM into container
577
+ if (parsed.dom) {
578
+ sandbox.container.innerHTML = parsed.dom;
579
+ }
878
580
 
879
- container.appendChild(errorContainer);
581
+ // 3. Inject styles into shadow root
582
+ const styleTarget = sandbox.shadowRoot || sandbox.container;
880
583
 
881
- } catch (renderError) {
882
- console.error(`[Wu] ๐Ÿ’ฅ Failed to render error state:`, renderError);
584
+ for (const cssText of parsed.styles.inline) {
585
+ const style = document.createElement('style');
586
+ style.textContent = cssText;
587
+ styleTarget.appendChild(style);
883
588
  }
884
- }
885
589
 
886
- /**
887
- * ๐Ÿ“ฆ REMOTE APP LOADER: Intelligent remote app loading with path resolution
888
- * @param {Object} app - Informaciรณn de la app
889
- * @param {Object} sandbox - Sandbox creado
890
- */
891
- async loadAndMountRemoteApp(app, sandbox) {
892
- // ๐Ÿ” SMART PATH RESOLUTION: Multi-path URL construction with validation
893
- const moduleUrl = await this.resolveModulePath(app);
590
+ for (const href of parsed.styles.external) {
591
+ const link = document.createElement('link');
592
+ link.rel = 'stylesheet';
593
+ link.href = href;
594
+ styleTarget.appendChild(link);
595
+ }
894
596
 
895
- console.log(`[Wu] ๐Ÿ“ฆ Loading ES module: ${moduleUrl}`);
597
+ // 4. Build and execute scripts inside the proxy
598
+ const scripts = [];
599
+ for (const content of parsed.scripts.inline) {
600
+ scripts.push({ content });
601
+ }
602
+ for (const src of parsed.scripts.external) {
603
+ scripts.push({ src });
604
+ }
896
605
 
897
- try {
898
- // ๐ŸŒŸ MODULE LOADING: Multi-path module resolution
899
- await this.moduleLoader(moduleUrl, app.name);
606
+ await this.scriptExecutor.executeAll(scripts, app.name, proxy);
607
+ logger.wuDebug(`[eval] Scripts executed for ${app.name}`);
900
608
 
901
- console.log(`[Wu] โœ… ES module loaded: ${app.name}`);
609
+ // 5. Wait for wu.define()
610
+ await this._waitForDefine(app.name, 'eval');
902
611
 
903
- } catch (error) {
904
- console.error(`[Wu] โŒ Failed to load ES module ${moduleUrl}:`, error);
905
- throw error;
612
+ logger.wuDebug(`[eval] ${app.name} loaded and registered`);
613
+ }
614
+
615
+ /**
616
+ * Wait for an app to call wu.define() with a timeout.
617
+ * Shared by strict and eval modes.
618
+ */
619
+ async _waitForDefine(appName, mode) {
620
+ const maxWaitTime = 10000;
621
+ const checkInterval = 50;
622
+ const startTime = Date.now();
623
+
624
+ while (!this.definitions.has(appName)) {
625
+ if (Date.now() - startTime >= maxWaitTime) {
626
+ throw new Error(
627
+ `[${mode}] App '${appName}' loaded but wu.define() was not called within ${maxWaitTime}ms.\n` +
628
+ `Make sure your app calls: window.wu.define('${appName}', { mount, unmount })`
629
+ );
630
+ }
631
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
906
632
  }
907
633
  }
908
634
 
909
635
  /**
910
- * ๐Ÿ” MODULE PATH RESOLVER: Intelligent URL construction with fallback
636
+ * Module path resolver: Intelligent URL construction with fallback
911
637
  * Intelligently resolves module paths with real-time validation
912
638
  */
913
639
  async resolveModulePath(app) {
914
640
  let entryFile = app.manifest?.entry || 'main.js';
915
641
  const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
916
642
 
917
- // ๐Ÿ”ง NORMALIZE PATH: Remove duplicated directories
643
+ // Normalize path: Remove duplicated directories
918
644
  // If entry already starts with 'src/', 'dist/', etc., use it as-is
919
645
  const hasFolderPrefix = /^(src|dist|public|build|assets|lib|es)\//.test(entryFile);
920
646
 
921
647
  if (hasFolderPrefix) {
922
- console.log(`[Wu] ๐Ÿ”ง Entry already has folder prefix: ${entryFile}`);
648
+ logger.wuDebug(`Entry already has folder prefix: ${entryFile}`);
923
649
  // Entry already has folder, just use baseUrl + entryFile
924
650
  const directPath = `${baseUrl}/${entryFile}`;
925
- console.log(`[Wu] ๐ŸŽฏ Using direct path: ${directPath}`);
651
+ logger.wuDebug(`Using direct path: ${directPath}`);
926
652
  return directPath;
927
653
  }
928
654
 
929
- // ๐Ÿ” Multi-path candidates (in order of preference)
655
+ // Multi-path candidates (in order of preference)
930
656
  const pathCandidates = [
931
657
  `${baseUrl}/src/${entryFile}`, // Standard structure
932
658
  `${baseUrl}/${entryFile}`, // Root level
@@ -938,47 +664,47 @@ export class WuCore {
938
664
  `${baseUrl}/es/${entryFile}` // ES modules folder
939
665
  ];
940
666
 
941
- console.log(`[Wu] ๐Ÿ” Attempting path resolution for ${app.name}...`);
667
+ logger.wuDebug(`Attempting path resolution for ${app.name}...`);
942
668
 
943
- // ๐Ÿš€ SMART PATH DISCOVERY: Try each candidate with validation
669
+ // Smart path discovery: Try each candidate with validation
944
670
  for (let i = 0; i < pathCandidates.length; i++) {
945
671
  const candidate = pathCandidates[i];
946
672
 
947
673
  try {
948
- console.log(`[Wu] ๐ŸŽฏ Testing path candidate ${i + 1}/${pathCandidates.length}: ${candidate}`);
674
+ logger.wuDebug(`Testing path candidate ${i + 1}/${pathCandidates.length}: ${candidate}`);
949
675
 
950
- // ๐ŸŒŸ Path validation with enhanced verification
676
+ // Path validation with enhanced verification
951
677
  const isValid = await this.validatePath(candidate);
952
678
 
953
679
  if (isValid) {
954
- console.log(`[Wu] โœ… Path resolved successfully: ${candidate}`);
680
+ logger.wuDebug(`Path resolved successfully: ${candidate}`);
955
681
  return candidate;
956
682
  } else {
957
- console.log(`[Wu] โŒ Path candidate ${i + 1} failed validation: ${candidate}`);
683
+ logger.wuDebug(`Path candidate ${i + 1} failed validation: ${candidate}`);
958
684
  }
959
685
 
960
686
  } catch (error) {
961
- console.log(`[Wu] โš ๏ธ Path candidate ${i + 1} threw error: ${candidate} - ${error.message}`);
687
+ logger.wuDebug(`Path candidate ${i + 1} threw error: ${candidate} - ${error.message}`);
962
688
  continue;
963
689
  }
964
690
  }
965
691
 
966
- // ๐ŸŒŒ FALLBACK: If all candidates fail, use the first one and let the error bubble up
692
+ // Fallback: If all candidates fail, use the first one and let the error bubble up
967
693
  const fallbackPath = pathCandidates[0];
968
- console.warn(`[Wu] ๐Ÿšจ All path candidates failed, using fallback: ${fallbackPath}`);
694
+ logger.wuWarn(`All path candidates failed, using fallback: ${fallbackPath}`);
969
695
  return fallbackPath;
970
696
  }
971
697
 
972
698
  /**
973
- * ๐Ÿ”ง PATH VALIDATOR: Smart existence verification with module testing
699
+ * Path validator: Smart existence verification with module testing
974
700
  * Validates if a path exists and can be loaded as an ES module
975
701
  */
976
702
  async validatePath(url) {
977
703
  try {
978
- // ๐ŸŒŸ ENHANCED VALIDATION: Try actual module import for reliable verification
979
- console.log(`[Wu] ๐Ÿ” Testing path: ${url}`);
704
+ // Enhanced validation: Try actual module import for reliable verification
705
+ logger.wuDebug(`Testing path: ${url}`);
980
706
 
981
- // ๐Ÿš€ First, try a GET request to check if file exists and is accessible
707
+ // First, try a GET request to check if file exists and is accessible
982
708
  const response = await fetch(url, {
983
709
  method: 'GET',
984
710
  cache: 'no-cache',
@@ -986,11 +712,11 @@ export class WuCore {
986
712
  });
987
713
 
988
714
  if (!response.ok) {
989
- console.log(`[Wu] โŒ Path validation failed - HTTP ${response.status}: ${url}`);
715
+ logger.wuDebug(`Path validation failed - HTTP ${response.status}: ${url}`);
990
716
  return false;
991
717
  }
992
718
 
993
- // ๐ŸŽฏ Check content type and file extension
719
+ // Check content type and file extension
994
720
  const contentType = response.headers.get('content-type') || '';
995
721
  const isJavaScript =
996
722
  contentType.includes('javascript') ||
@@ -1000,14 +726,14 @@ export class WuCore {
1000
726
  url.endsWith('.mjs');
1001
727
 
1002
728
  if (!isJavaScript) {
1003
- console.log(`[Wu] โŒ Path validation failed - Invalid content type '${contentType}': ${url}`);
729
+ logger.wuDebug(`Path validation failed - Invalid content type '${contentType}': ${url}`);
1004
730
  return false;
1005
731
  }
1006
732
 
1007
- // ๐ŸŒŒ FINAL VERIFICATION: Check if content looks like a valid module
733
+ // Final verification: Check if content looks like a valid module
1008
734
  const content = await response.text();
1009
735
 
1010
- // ๐Ÿšซ DETECT HTML FALLBACK: Check if server returned HTML instead of JS
736
+ // Detect HTML fallback: Check if server returned HTML instead of JS
1011
737
  // Only check if content STARTS with HTML markers (trimmed), not if it contains them anywhere
1012
738
  // This avoids false positives for Angular/React bundles that contain template strings
1013
739
  const trimmedContent = content.trim().toLowerCase();
@@ -1019,11 +745,11 @@ export class WuCore {
1019
745
  trimmedContent.startsWith('<!-');
1020
746
 
1021
747
  if (isHtmlFallback) {
1022
- console.log(`[Wu] โŒ Path validation failed - Server returned HTML fallback page: ${url}`);
748
+ logger.wuDebug(`Path validation failed - Server returned HTML fallback page: ${url}`);
1023
749
  return false;
1024
750
  }
1025
751
 
1026
- // ๐ŸŽฏ Check for valid JavaScript module content
752
+ // Check for valid JavaScript module content
1027
753
  const hasModuleContent =
1028
754
  content.includes('export') ||
1029
755
  content.includes('import') ||
@@ -1033,44 +759,44 @@ export class WuCore {
1033
759
  (content.includes('function') && content.length > 10);
1034
760
 
1035
761
  if (!hasModuleContent) {
1036
- console.log(`[Wu] โŒ Path validation failed - No valid module content: ${url}`);
1037
- console.log(`[Wu] ๐Ÿ” Content preview: ${content.substring(0, 100)}...`);
762
+ logger.wuDebug(`Path validation failed - No valid module content: ${url}`);
763
+ logger.wuDebug(`Content preview: ${content.substring(0, 100)}...`);
1038
764
  return false;
1039
765
  }
1040
766
 
1041
- console.log(`[Wu] โœ… Path validation successful: ${url} (${content.length} chars)`);
767
+ logger.wuDebug(`Path validation successful: ${url} (${content.length} chars)`);
1042
768
  return true;
1043
769
 
1044
770
  } catch (error) {
1045
- // ๐Ÿšซ Network, timeout, or parsing error means path is invalid
1046
- console.log(`[Wu] โŒ Path validation failed for ${url}: ${error.message}`);
771
+ // Network, timeout, or parsing error means path is invalid
772
+ logger.wuDebug(`Path validation failed for ${url}: ${error.message}`);
1047
773
  return false;
1048
774
  }
1049
775
  }
1050
776
 
1051
777
  /**
1052
- * ๐Ÿ“ฆ MODULE LOADER: Advanced registration patterns
778
+ * Module loader: Advanced registration patterns
1053
779
  * Handles asynchronous registration with timing synchronization
1054
- * FIX: Verifica que definitions tenga el lifecycle despuรฉs de cargar
780
+ * Verifica que definitions tenga el lifecycle despues de cargar
1055
781
  */
1056
782
  async moduleLoader(moduleUrl, appName) {
1057
- // โœ… Check if already registered
783
+ // Check if already registered
1058
784
  if (this.definitions.has(appName)) {
1059
- console.log(`[Wu] โšก App ${appName} already registered`);
785
+ logger.wuDebug(`App ${appName} already registered`);
1060
786
  return;
1061
787
  }
1062
788
 
1063
- console.log(`[Wu] ๐Ÿ“ก Using event-based registration for ${appName}`);
789
+ logger.wuDebug(`Using event-based registration for ${appName}`);
1064
790
 
1065
- // ๐Ÿš€ Load module first
791
+ // Load module first
1066
792
  try {
1067
793
  await import(/* @vite-ignore */ moduleUrl);
1068
794
  } catch (loadError) {
1069
- console.error(`[Wu] โŒ Failed to import module ${moduleUrl}:`, loadError);
795
+ logger.wuError(`Failed to import module ${moduleUrl}:`, loadError);
1070
796
  throw loadError;
1071
797
  }
1072
798
 
1073
- // ๐Ÿ›ก๏ธ FIX: Esperar a que wu.define() se ejecute con verificaciรณn real
799
+ // Wait for wu.define() to be called with real verification
1074
800
  const maxWaitTime = 10000; // 10 segundos
1075
801
  const checkInterval = 50; // Verificar cada 50ms
1076
802
  const startTime = Date.now();
@@ -1092,34 +818,62 @@ export class WuCore {
1092
818
  await new Promise(resolve => setTimeout(resolve, checkInterval));
1093
819
  }
1094
820
 
1095
- console.log(`[Wu] โœ… App ${appName} loaded and registered (verified in definitions)`);
821
+ logger.wuDebug(`App ${appName} loaded and registered (verified in definitions)`);
1096
822
  }
1097
823
 
1098
824
  /**
1099
- * Desmontar una aplicaciรณn
825
+ * Desmontar una aplicacion.
826
+ *
827
+ * With keepAlive, the app is hidden instead of destroyed.
828
+ * All DOM, JS state, timers, and iframe are preserved.
829
+ * Re-mounting shows the app instantly.
830
+ *
831
+ * keepAlive is resolved from (in priority order):
832
+ * 1. options.keepAlive (per-call override)
833
+ * 2. app config keepAlive (set via wu.app() or registerApp)
834
+ * 3. false (default: destroy)
835
+ *
836
+ * Use options.force = true to destroy even if keepAlive is set.
837
+ *
1100
838
  * @param {string} appName - Nombre de la app
839
+ * @param {Object} [options] - Unmount options
840
+ * @param {boolean} [options.keepAlive] - Preserve state for instant re-mount
841
+ * @param {boolean} [options.force] - Force destroy even if keepAlive
1101
842
  */
1102
- async unmount(appName) {
843
+ async unmount(appName, options = {}) {
1103
844
  try {
1104
- console.log(`[Wu] ๐Ÿ—‘๏ธ Unmounting ${appName}`);
845
+ logger.wuDebug(`Unmounting ${appName}`);
1105
846
 
1106
847
  const mounted = this.mounted.get(appName);
1107
848
  if (!mounted) {
1108
- console.warn(`[Wu] App ${appName} not mounted`);
849
+ // Check if it's hidden (keep-alive) โ€” force destroy if requested
850
+ if (options.force && this.hidden.has(appName)) {
851
+ return await this._destroyHidden(appName);
852
+ }
853
+ logger.wuWarn(`App ${appName} not mounted`);
1109
854
  return;
1110
855
  }
1111
856
 
1112
- // ๐Ÿช Execute beforeUnmount hooks
857
+ // Resolve keepAlive: per-call > per-app config > default false
858
+ const keepAlive = options.force
859
+ ? false
860
+ : (options.keepAlive ?? mounted.app?.keepAlive ?? false);
861
+
862
+ if (keepAlive) {
863
+ return await this.hide(appName);
864
+ }
865
+
866
+ // Execute beforeUnmount hooks
1113
867
  const beforeUnmountResult = await this.hooks.execute('beforeUnmount', { appName, mounted });
1114
868
  if (beforeUnmountResult.cancelled) {
1115
- console.warn('[Wu] Unmount cancelled by beforeUnmount hook');
869
+ logger.wuWarn('Unmount cancelled by beforeUnmount hook');
1116
870
  return;
1117
871
  }
1118
872
 
1119
- // ๐Ÿ”Œ Call plugin beforeUnmount hooks
873
+ // Call plugin beforeUnmount hooks
1120
874
  const pluginBeforeUnmount = await this.pluginSystem.callHook('beforeUnmount', { appName });
1121
875
  if (pluginBeforeUnmount === false) {
1122
- console.warn('[Wu] Unmount cancelled by plugin beforeUnmount hook');
876
+ logger.wuWarn('Unmount cancelled by plugin beforeUnmount hook');
1123
877
  return;
1124
878
  }
1125
879
 
@@ -1128,42 +882,197 @@ export class WuCore {
1128
882
  await mounted.lifecycle.unmount(mounted.container);
1129
883
  }
1130
884
 
1131
- // Limpiar sandbox
1132
- this.sandbox.cleanup(mounted.sandbox);
1133
-
1134
- // ๐ŸŠ Release sandbox to pool
1135
- if (mounted.poolSandbox) {
1136
- this.sandboxPool.release(appName);
885
+ // Destroy iframe sandbox if present (strict mode)
886
+ if (mounted.sandbox.iframeSandbox) {
887
+ mounted.sandbox.iframeSandbox.destroy();
888
+ mounted.sandbox.iframeSandbox = null;
1137
889
  }
1138
890
 
1139
- // ๐Ÿ”“ Dejar de observar contenedor (evita memory leak)
1140
- this.unobserveContainer(appName);
891
+ // Limpiar sandbox
892
+ this.sandbox.cleanup(mounted.sandbox);
1141
893
 
1142
894
  // Remover del registro de montadas
1143
895
  this.mounted.delete(appName);
1144
896
 
1145
- // ๐Ÿช Execute afterUnmount hooks
897
+ // Execute afterUnmount hooks
1146
898
  await this.hooks.execute('afterUnmount', { appName });
1147
899
 
1148
- // ๐Ÿ”Œ Call plugin afterUnmount hooks
900
+ // Call plugin afterUnmount hooks
1149
901
  await this.pluginSystem.callHook('afterUnmount', { appName });
1150
902
 
1151
- // ๐Ÿ“ข Emit unmount event
903
+ // Emit unmount event
1152
904
  this.eventBus.emit('app:unmounted', { appName }, { appName });
1153
905
 
1154
- console.log(`[Wu] โœ… ${appName} unmounted successfully`);
906
+ logger.wuDebug(`${appName} unmounted successfully`);
1155
907
  } catch (error) {
1156
- console.error(`[Wu] โŒ Failed to unmount ${appName}:`, error);
908
+ logger.wuError(`Failed to unmount ${appName}:`, error);
1157
909
 
1158
- // ๐Ÿ”Œ Call plugin error hooks
910
+ // Call plugin error hooks
1159
911
  await this.pluginSystem.callHook('onError', { phase: 'unmount', error, appName });
1160
912
 
1161
- // ๐Ÿ“ข Emit error event
913
+ // Emit error event
1162
914
  this.eventBus.emit('app:error', { appName, error: error.message }, { appName });
1163
915
  throw error;
1164
916
  }
1165
917
  }
1166
918
 
919
+ /**
920
+ * Hide a mounted app (keep-alive).
921
+ *
922
+ * Preserves all state: DOM in Shadow DOM, JS in iframe, timers, listeners.
923
+ * The app's optional `deactivate()` lifecycle hook is called.
924
+ * Re-show with `show()` or `mount()` with the same container.
925
+ *
926
+ * @param {string} appName - App to hide
927
+ */
928
+ async hide(appName) {
929
+ const mounted = this.mounted.get(appName);
930
+ if (!mounted) {
931
+ logger.wuWarn(`Cannot hide ${appName}: not mounted`);
932
+ return;
933
+ }
934
+
935
+ logger.wuDebug(`Hiding ${appName} (keep-alive)`);
936
+
937
+ // Call optional deactivate lifecycle hook
938
+ if (mounted.lifecycle?.deactivate) {
939
+ try {
940
+ await mounted.lifecycle.deactivate(mounted.container);
941
+ } catch (err) {
942
+ logger.wuWarn(`deactivate() failed for ${appName}:`, err);
943
+ }
944
+ }
945
+
946
+ // Execute beforeUnmount hooks (so plugins know)
947
+ await this.hooks.execute('beforeUnmount', { appName, mounted, keepAlive: true });
948
+ await this.pluginSystem.callHook('beforeUnmount', { appName, keepAlive: true });
949
+
950
+ // Hide the host container โ€” all Shadow DOM content stays intact
951
+ mounted.hostContainer.style.display = 'none';
952
+ mounted.state = 'hidden';
953
+ mounted.hiddenAt = Date.now();
954
+
955
+ // Move from mounted โ†’ hidden
956
+ this.hidden.set(appName, mounted);
957
+ this.mounted.delete(appName);
958
+
959
+ // Execute afterUnmount hooks
960
+ await this.hooks.execute('afterUnmount', { appName, keepAlive: true });
961
+ await this.pluginSystem.callHook('afterUnmount', { appName, keepAlive: true });
962
+
963
+ // Emit event
964
+ this.eventBus.emit('app:hidden', { appName }, { appName });
965
+
966
+ logger.wuInfo(`${appName} hidden (keep-alive) โ€” state preserved`);
967
+ }
968
+
969
+ /**
970
+ * Show a hidden (keep-alive) app.
971
+ *
972
+ * Restores visibility instantly โ€” no reload, no remount.
973
+ * The app's optional `activate()` lifecycle hook is called.
974
+ *
975
+ * @param {string} appName - App to show
976
+ */
977
+ async show(appName) {
978
+ const hidden = this.hidden.get(appName);
979
+ if (!hidden) {
980
+ logger.wuWarn(`Cannot show ${appName}: not in keep-alive state`);
981
+ return;
982
+ }
983
+
984
+ this.performance.startMeasure('show', appName);
985
+ logger.wuDebug(`Showing ${appName} from keep-alive`);
986
+
987
+ // Execute beforeMount hooks
988
+ await this.hooks.execute('beforeMount', {
989
+ appName,
990
+ containerSelector: hidden.containerSelector,
991
+ sandbox: hidden.sandbox,
992
+ lifecycle: hidden.lifecycle,
993
+ keepAlive: true
994
+ });
995
+ await this.pluginSystem.callHook('beforeMount', {
996
+ appName,
997
+ containerSelector: hidden.containerSelector,
998
+ keepAlive: true
999
+ });
1000
+
1001
+ // Show the host container
1002
+ hidden.hostContainer.style.display = '';
1003
+ hidden.state = 'stable';
1004
+ delete hidden.hiddenAt;
1005
+
1006
+ // Move from hidden โ†’ mounted
1007
+ this.mounted.set(appName, hidden);
1008
+ this.hidden.delete(appName);
1009
+
1010
+ // Call optional activate lifecycle hook
1011
+ if (hidden.lifecycle?.activate) {
1012
+ try {
1013
+ await hidden.lifecycle.activate(hidden.container);
1014
+ } catch (err) {
1015
+ logger.wuWarn(`activate() failed for ${appName}:`, err);
1016
+ }
1017
+ }
1018
+
1019
+ const showTime = this.performance.endMeasure('show', appName);
1020
+
1021
+ // Execute afterMount hooks
1022
+ await this.hooks.execute('afterMount', {
1023
+ appName,
1024
+ containerSelector: hidden.containerSelector,
1025
+ sandbox: hidden.sandbox,
1026
+ mountTime: showTime,
1027
+ keepAlive: true
1028
+ });
1029
+ await this.pluginSystem.callHook('afterMount', {
1030
+ appName,
1031
+ containerSelector: hidden.containerSelector,
1032
+ mountTime: showTime,
1033
+ keepAlive: true
1034
+ });
1035
+
1036
+ // Emit event
1037
+ this.eventBus.emit('app:shown', { appName, showTime }, { appName });
1038
+
1039
+ logger.wuInfo(`${appName} shown from keep-alive in ${showTime.toFixed(2)}ms`);
1040
+ }
1041
+
1042
+ /**
1043
+ * Force-destroy a hidden (keep-alive) app.
1044
+ * Runs full cleanup: lifecycle unmount, iframe destroy, sandbox cleanup.
1045
+ *
1046
+ * @param {string} appName
1047
+ * @private
1048
+ */
1049
+ async _destroyHidden(appName) {
1050
+ const hidden = this.hidden.get(appName);
1051
+ if (!hidden) return;
1052
+
1053
+ logger.wuDebug(`Force-destroying hidden app: ${appName}`);
1054
+
1055
+ // Show first (so unmount sees the container)
1056
+ hidden.hostContainer.style.display = '';
1057
+ hidden.state = 'stable';
1058
+
1059
+ // Move back to mounted temporarily
1060
+ this.mounted.set(appName, hidden);
1061
+ this.hidden.delete(appName);
1062
+
1063
+ // Now do a full unmount
1064
+ await this.unmount(appName, { force: true });
1065
+ }
1066
+
1067
+ /**
1068
+ * Check if an app is in keep-alive (hidden) state.
1069
+ * @param {string} appName
1070
+ * @returns {boolean}
1071
+ */
1072
+ isHidden(appName) {
1073
+ return this.hidden.has(appName);
1074
+ }
1075
+
1167
1076
  /**
1168
1077
  * Cargar componente compartido (para imports/exports)
1169
1078
  * @param {string} componentPath - Ruta del componente (ej: "shared.Button")
@@ -1192,7 +1101,7 @@ export class WuCore {
1192
1101
  }
1193
1102
 
1194
1103
  /**
1195
- * Obtener informaciรณn de una app
1104
+ * Obtener informacion de una app
1196
1105
  * @param {string} appName - Nombre de la app
1197
1106
  */
1198
1107
  getAppInfo(appName) {
@@ -1205,19 +1114,20 @@ export class WuCore {
1205
1114
  }
1206
1115
 
1207
1116
  /**
1208
- * Obtener estadรญsticas del framework
1117
+ * Obtener estadisticas del framework
1209
1118
  */
1210
1119
  getStats() {
1211
1120
  return {
1212
1121
  registered: this.apps.size,
1213
1122
  defined: this.definitions.size,
1214
1123
  mounted: this.mounted.size,
1124
+ hidden: this.hidden.size,
1215
1125
  apps: Array.from(this.apps.keys())
1216
1126
  };
1217
1127
  }
1218
1128
 
1219
1129
  /**
1220
- * ๐Ÿช STORE METHODS: Convenience methods for state management
1130
+ * Store methods: Convenience methods for state management
1221
1131
  */
1222
1132
 
1223
1133
  /**
@@ -1274,7 +1184,79 @@ export class WuCore {
1274
1184
  }
1275
1185
 
1276
1186
  /**
1277
- * ๐ŸŽฏ SIMPLIFIED API: Create WuApp instance for declarative usage
1187
+ * Set a URL override for an app (QA/testing).
1188
+ * Sets a cookie so the override persists across page reloads.
1189
+ * Only affects the current browser โ€” no one else sees it.
1190
+ *
1191
+ * @param {string} appName - App to override
1192
+ * @param {string} url - Override URL (e.g., 'http://localhost:5173')
1193
+ * @param {Object} [options]
1194
+ * @param {number} [options.maxAge=86400] - Cookie lifetime in seconds (default: 24h)
1195
+ *
1196
+ * @example
1197
+ * wu.override('cart', 'http://localhost:5173');
1198
+ * wu.override('header', 'https://preview-abc123.vercel.app');
1199
+ */
1200
+ override(appName, url, options) {
1201
+ this.overrides.set(appName, url, options);
1202
+ }
1203
+
1204
+ /**
1205
+ * Remove URL override for an app.
1206
+ * @param {string} appName
1207
+ */
1208
+ removeOverride(appName) {
1209
+ this.overrides.remove(appName);
1210
+ }
1211
+
1212
+ /**
1213
+ * Get all active overrides.
1214
+ * @returns {Object} { appName: url, ... }
1215
+ */
1216
+ getOverrides() {
1217
+ return this.overrides.getAll();
1218
+ }
1219
+
1220
+ /**
1221
+ * Remove all overrides.
1222
+ */
1223
+ clearOverrides() {
1224
+ this.overrides.clearAll();
1225
+ }
1226
+
1227
+ /**
1228
+ * Prefetch one or more apps before they're needed.
1229
+ *
1230
+ * Uses Speculation Rules API (Chrome 121+), falls back to
1231
+ * <link rel="modulepreload"> or <link rel="prefetch">.
1232
+ *
1233
+ * @param {string|string[]} appNames - App name(s) to prefetch
1234
+ * @param {Object} [options]
1235
+ * @param {'immediate'|'hover'|'visible'|'idle'} [options.on='immediate'] - When to trigger
1236
+ * @param {string|Element} [options.target] - Element for hover/visible triggers
1237
+ * @param {'conservative'|'moderate'|'eager'} [options.eagerness='moderate'] - Speculation eagerness
1238
+ * @returns {Promise<void>|Function} Promise or cleanup function
1239
+ *
1240
+ * @example
1241
+ * wu.prefetch('cart');
1242
+ * wu.prefetch('cart', { on: 'hover', target: '#cart-link' });
1243
+ * wu.prefetch('cart', { on: 'visible', target: '#cart-section' });
1244
+ * wu.prefetch(['profile', 'settings'], { on: 'idle' });
1245
+ */
1246
+ prefetch(appNames, options) {
1247
+ return this.prefetcher.prefetch(appNames, options);
1248
+ }
1249
+
1250
+ /**
1251
+ * Prefetch all registered but not-yet-mounted apps.
1252
+ * @param {Object} [options] - Same options as prefetch()
1253
+ */
1254
+ prefetchAll(options) {
1255
+ return this.prefetcher.prefetchAll(options);
1256
+ }
1257
+
1258
+ /**
1259
+ * Create WuApp instance for declarative usage
1278
1260
  * @param {string} name - App name
1279
1261
  * @param {Object} config - Configuration { url, container, autoInit }
1280
1262
  * @returns {WuApp} WuApp instance
@@ -1287,71 +1269,57 @@ export class WuCore {
1287
1269
  * Limpiar todo el framework
1288
1270
  */
1289
1271
  async destroy() {
1290
- console.log('[Wu] ๐Ÿงน Destroying framework...');
1272
+ logger.wuDebug('Destroying framework...');
1291
1273
 
1292
1274
  try {
1293
- // ๐Ÿช Execute beforeDestroy hooks
1275
+ // Execute beforeDestroy hooks
1294
1276
  await this.hooks.execute('beforeDestroy', {});
1295
1277
 
1296
- // ๐Ÿ”Œ Call plugin onDestroy hooks
1278
+ // Call plugin onDestroy hooks
1297
1279
  await this.pluginSystem.callHook('onDestroy', {});
1298
1280
 
1299
- // Limpiar health monitor
1300
- if (this.healthState.monitor) {
1301
- clearInterval(this.healthState.monitor);
1302
- this.healthState.monitor = null;
1303
- }
1304
-
1305
- // Limpiar todos los MutationObservers por contenedor
1306
- if (this.healthState.containerObservers) {
1307
- for (const [appName, observer] of this.healthState.containerObservers) {
1308
- observer.disconnect();
1309
- }
1310
- this.healthState.containerObservers.clear();
1311
- }
1312
-
1313
- // Limpiar timeouts pendientes
1314
- if (this.healthState.mutationCheckTimeout) {
1315
- clearTimeout(this.healthState.mutationCheckTimeout);
1281
+ // Force-destroy all hidden (keep-alive) apps first
1282
+ for (const appName of [...this.hidden.keys()]) {
1283
+ await this._destroyHidden(appName);
1316
1284
  }
1317
1285
 
1318
1286
  // Desmontar todas las apps
1319
- for (const appName of this.mounted.keys()) {
1320
- await this.unmount(appName);
1287
+ for (const appName of [...this.mounted.keys()]) {
1288
+ await this.unmount(appName, { force: true });
1321
1289
  }
1322
1290
 
1323
- // ๐Ÿงน Limpiar sistemas esenciales
1291
+ // Limpiar sistemas esenciales
1324
1292
  this.cache.clear();
1325
1293
  this.eventBus.removeAll();
1326
1294
  this.eventBus.clearHistory();
1327
1295
  this.performance.clearMetrics();
1328
1296
 
1329
- // ๐Ÿงน Limpiar advanced systems
1297
+ // Limpiar advanced systems
1330
1298
  this.pluginSystem.cleanup();
1331
1299
  this.strategies.cleanup();
1332
1300
  this.errorBoundary.cleanup();
1333
1301
  this.hooks.cleanup();
1334
- this.sandboxPool.cleanup();
1335
- this.registry.cleanup();
1302
+ this.prefetcher.cleanup();
1336
1303
 
1337
1304
  // Limpiar registros
1338
1305
  this.apps.clear();
1339
1306
  this.definitions.clear();
1340
1307
  this.manifests.clear();
1341
1308
  this.mounted.clear();
1309
+ this.hidden.clear();
1342
1310
 
1343
1311
  // Limpiar store
1344
1312
  this.store.clear();
1345
1313
 
1346
1314
  this.isInitialized = false;
1347
1315
 
1348
- // ๐Ÿช Execute afterDestroy hooks
1316
+ // Execute afterDestroy hooks
1349
1317
  await this.hooks.execute('afterDestroy', {});
1350
1318
 
1351
- console.log('[Wu] โœ… Framework destroyed');
1319
+ logger.wuDebug('Framework destroyed');
1352
1320
  } catch (error) {
1353
- console.error('[Wu] โŒ Error during destroy:', error);
1321
+ logger.wuError('Error during destroy:', error);
1354
1322
  throw error;
1355
1323
  }
1356
1324
  }
1357
- }
1325
+ }