wu-framework 1.1.7 โ†’ 1.1.9

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