@zolomedia/bifrost-client 1.7.74

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 (140) hide show
  1. package/L1_Foundation/L1_Foundation.js +13 -0
  2. package/L1_Foundation/bootstrap/bootstrap.js +11 -0
  3. package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
  4. package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
  5. package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
  6. package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
  7. package/L1_Foundation/bootstrap/module_registry.js +102 -0
  8. package/L1_Foundation/bootstrap/prism_loader.js +164 -0
  9. package/L1_Foundation/config/client_config.js +110 -0
  10. package/L1_Foundation/config/config.js +7 -0
  11. package/L1_Foundation/connection/connection.js +8 -0
  12. package/L1_Foundation/connection/websocket_connection.js +122 -0
  13. package/L1_Foundation/constants/bifrost_constants.js +284 -0
  14. package/L1_Foundation/constants/constants.js +7 -0
  15. package/L1_Foundation/logger/logger.js +10 -0
  16. package/L2_Handling/L2_Handling.js +15 -0
  17. package/L2_Handling/cache/cache.js +22 -0
  18. package/L2_Handling/cache/cache_constants.js +69 -0
  19. package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
  20. package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
  21. package/L2_Handling/cache/orchestration/orchestration.js +12 -0
  22. package/L2_Handling/cache/storage/session_manager.js +289 -0
  23. package/L2_Handling/cache/storage/storage.js +10 -0
  24. package/L2_Handling/cache/storage/storage_manager.js +590 -0
  25. package/L2_Handling/display/composite/composite.js +13 -0
  26. package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
  27. package/L2_Handling/display/composite/swiper_renderer.js +564 -0
  28. package/L2_Handling/display/composite/terminal_renderer.js +922 -0
  29. package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
  30. package/L2_Handling/display/display.js +30 -0
  31. package/L2_Handling/display/feedback/feedback.js +11 -0
  32. package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
  33. package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
  34. package/L2_Handling/display/inputs/button_renderer.js +634 -0
  35. package/L2_Handling/display/inputs/form_renderer.js +583 -0
  36. package/L2_Handling/display/inputs/input_renderer.js +658 -0
  37. package/L2_Handling/display/inputs/inputs.js +12 -0
  38. package/L2_Handling/display/navigation/menu_renderer.js +206 -0
  39. package/L2_Handling/display/navigation/navigation.js +11 -0
  40. package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
  41. package/L2_Handling/display/orchestration/orchestration.js +11 -0
  42. package/L2_Handling/display/orchestration/renderer.js +430 -0
  43. package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
  44. package/L2_Handling/display/outputs/alert_renderer.js +161 -0
  45. package/L2_Handling/display/outputs/audio_renderer.js +94 -0
  46. package/L2_Handling/display/outputs/card_renderer.js +229 -0
  47. package/L2_Handling/display/outputs/code_renderer.js +66 -0
  48. package/L2_Handling/display/outputs/dl_renderer.js +131 -0
  49. package/L2_Handling/display/outputs/header_renderer.js +162 -0
  50. package/L2_Handling/display/outputs/icon_renderer.js +107 -0
  51. package/L2_Handling/display/outputs/image_renderer.js +145 -0
  52. package/L2_Handling/display/outputs/list_renderer.js +190 -0
  53. package/L2_Handling/display/outputs/outputs.js +19 -0
  54. package/L2_Handling/display/outputs/table_renderer.js +765 -0
  55. package/L2_Handling/display/outputs/text_renderer.js +818 -0
  56. package/L2_Handling/display/outputs/typography_renderer.js +293 -0
  57. package/L2_Handling/display/outputs/video_renderer.js +116 -0
  58. package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
  59. package/L2_Handling/display/primitives/form_primitives.js +526 -0
  60. package/L2_Handling/display/primitives/generic_containers.js +109 -0
  61. package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
  62. package/L2_Handling/display/primitives/link_primitives.js +552 -0
  63. package/L2_Handling/display/primitives/lists_primitives.js +262 -0
  64. package/L2_Handling/display/primitives/media_primitives.js +383 -0
  65. package/L2_Handling/display/primitives/primitives.js +19 -0
  66. package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
  67. package/L2_Handling/display/primitives/table_primitives.js +528 -0
  68. package/L2_Handling/display/primitives/typography_primitives.js +175 -0
  69. package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
  70. package/L2_Handling/display/specialized/specialized.js +10 -0
  71. package/L2_Handling/hooks/hooks.js +9 -0
  72. package/L2_Handling/hooks/menu_integration.js +57 -0
  73. package/L2_Handling/hooks/widget_hook_manager.js +292 -0
  74. package/L2_Handling/message/message.js +8 -0
  75. package/L2_Handling/message/message_handler.js +701 -0
  76. package/L2_Handling/navigation/navigation.js +8 -0
  77. package/L2_Handling/navigation/navigation_manager.js +403 -0
  78. package/L2_Handling/zhooks/features/cache_live.js +287 -0
  79. package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
  80. package/L2_Handling/zhooks/zhooks_manager.js +65 -0
  81. package/L2_Handling/zvaf/zvaf.js +8 -0
  82. package/L2_Handling/zvaf/zvaf_manager.js +334 -0
  83. package/L3_Abstraction/L3_Abstraction.js +12 -0
  84. package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
  85. package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
  86. package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
  87. package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
  88. package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
  89. package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
  90. package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
  91. package/L3_Abstraction/renderer/renderer.js +1 -0
  92. package/L3_Abstraction/session/session.js +1 -0
  93. package/L4_Orchestration/L4_Orchestration.js +11 -0
  94. package/L4_Orchestration/client/client.js +1 -0
  95. package/L4_Orchestration/facade/facade.js +9 -0
  96. package/L4_Orchestration/facade/manager_registry.js +118 -0
  97. package/L4_Orchestration/facade/renderer_registry.js +274 -0
  98. package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
  99. package/L4_Orchestration/lifecycle/initializer.js +135 -0
  100. package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
  101. package/L4_Orchestration/rendering/facade.js +94 -0
  102. package/L4_Orchestration/rendering/rendering.js +7 -0
  103. package/LICENSE +21 -0
  104. package/README.md +82 -0
  105. package/bifrost_client.js +204 -0
  106. package/bifrost_core.js +1686 -0
  107. package/docs/ARCHITECTURE.md +111 -0
  108. package/docs/PROTOCOL.md +106 -0
  109. package/docs/RENDERERS.md +101 -0
  110. package/docs/SECURITY.md +92 -0
  111. package/package.json +24 -0
  112. package/syntax/prism-zconfig.js +41 -0
  113. package/syntax/prism-zenv.js +69 -0
  114. package/syntax/prism-zolo-theme.css +288 -0
  115. package/syntax/prism-zolo.js +380 -0
  116. package/syntax/prism-zschema.js +38 -0
  117. package/syntax/prism-zspark.js +25 -0
  118. package/syntax/prism-zui.js +68 -0
  119. package/zSys/accessibility/accessibility.js +10 -0
  120. package/zSys/accessibility/emoji_accessibility.js +173 -0
  121. package/zSys/dom/block_utils.js +122 -0
  122. package/zSys/dom/container_utils.js +370 -0
  123. package/zSys/dom/dom.js +13 -0
  124. package/zSys/dom/dom_utils.js +328 -0
  125. package/zSys/dom/encoding_utils.js +117 -0
  126. package/zSys/dom/style_utils.js +71 -0
  127. package/zSys/errors/error_display.js +299 -0
  128. package/zSys/errors/errors.js +10 -0
  129. package/zSys/theme/color_utils.js +274 -0
  130. package/zSys/theme/dark_mode_utils.js +272 -0
  131. package/zSys/theme/size_utils.js +256 -0
  132. package/zSys/theme/spacing_utils.js +405 -0
  133. package/zSys/theme/theme.js +14 -0
  134. package/zSys/theme/zbase.css +1735 -0
  135. package/zSys/theme/zbase_inject.js +161 -0
  136. package/zSys/theme/ztheme_utils.js +305 -0
  137. package/zSys/validation/error_boundary.js +201 -0
  138. package/zSys/validation/validation.js +11 -0
  139. package/zSys/validation/validation_utils.js +238 -0
  140. package/zSys/zSys.js +14 -0
@@ -0,0 +1,334 @@
1
+ /**
2
+ * ZVaFManager - Manages zVaF elements (badge, navbar, content area)
3
+ *
4
+ * Responsibilities:
5
+ * - Initialize zVaF elements (zBifrostBadge, zNavBar, zVaF)
6
+ * - Populate connection badge
7
+ * - Update badge state (connecting, connected, disconnected, error)
8
+ * - Populate navbar from embedded config or API
9
+ * - Fetch fresh navbar after auth state changes
10
+ *
11
+ * Extracted from bifrost_client.js (Phase 3.2)
12
+ */
13
+
14
+ import { NavBarBuilder } from '../../L3_Abstraction/orchestrator/navbar_builder.js';
15
+
16
+ export class ZVaFManager {
17
+ constructor(client) {
18
+ this.client = client;
19
+ this.logger = client.logger;
20
+ this.options = client.options;
21
+ this.zuiConfig = client.zuiConfig;
22
+ }
23
+
24
+ /**
25
+ * Initialize zVaF elements.
26
+ *
27
+ * SSOT (hard cut): <zVaF> is the bifrost ROOT — the single mount an app template
28
+ * provides. EVERYTHING bifrost-generated lives under it, never as stray <body>
29
+ * children:
30
+ *
31
+ * <zVaF> ← root (template owns this tag only)
32
+ * <zNavBar>…</zNavBar> ← chrome (runtime-created), sticky top
33
+ * <div id="zVaF-content">…</div>← the render/clear target (page content)
34
+ * <zBifrostBadge>…</zBifrostBadge> ← chrome (runtime-created), fixed
35
+ * <div class="bifrost-error-container">…</div> ← error toasts (lazy)
36
+ *
37
+ * Chrome are SIBLINGS of the content host, so re-renders (which wipe the content
38
+ * host) never destroy them. Position/look come purely from built-in zClass in
39
+ * zbase.css (navbar sticky, badge/errors fixed).
40
+ */
41
+ initZVaFElements() {
42
+ this.logger.debug('[ZVaFManager] Starting initialization');
43
+
44
+ if (typeof document === 'undefined') {
45
+ this.logger.warn('[ZVaFManager] Not in browser environment');
46
+ return;
47
+ }
48
+
49
+ // The <zVaF> ROOT is the only required mount (template owns the bare tag).
50
+ const zVaFRoot = document.querySelector(this.options.targetElement) ||
51
+ document.getElementById(this.options.targetElement);
52
+ if (!zVaFRoot) {
53
+ this.logger.error(`[ZVaFManager] [ERROR] <${this.options.targetElement}> root not found in DOM`);
54
+ return;
55
+ }
56
+ this.client._zVaFRoot = zVaFRoot;
57
+ this.logger.debug('[ZVaFManager] zVaF root found');
58
+
59
+ // Inner content host (#zVaF-content) — THE render/clear target. Created inside
60
+ // the root so chrome siblings survive content re-renders.
61
+ let contentHost = zVaFRoot.querySelector('#zVaF-content');
62
+ if (!contentHost) {
63
+ contentHost = document.createElement('div');
64
+ contentHost.id = 'zVaF-content';
65
+ contentHost.className = 'zVaF-content';
66
+ zVaFRoot.appendChild(contentHost);
67
+ this.logger.debug('[ZVaFManager] Created content host #zVaF-content');
68
+ }
69
+ this.client._zVaFElement = contentHost;
70
+
71
+ // NavBar: sticky-top chrome → FIRST child of the root (above content).
72
+ const navElement = this._ensureChromeHost('zNavBar', (el) => {
73
+ zVaFRoot.insertBefore(el, zVaFRoot.firstChild);
74
+ });
75
+ if (navElement) {
76
+ this.client._zNavBarElement = navElement;
77
+ // Populate asynchronously (don't block initialization).
78
+ this.populateNavBar().catch(err => {
79
+ this.logger.error('[ZVaFManager] Failed to populate navbar:', err);
80
+ });
81
+ this.logger.debug('[ZVaFManager] NavBar host ready, populating');
82
+ }
83
+
84
+ // Badge: fixed chrome → appended to the root (DOM position is cosmetic; it's
85
+ // viewport-fixed via zbase.css). The badge is a core zHook — ON by default,
86
+ // opt out with zHooks: { badge: false }. When off, the element is never
87
+ // created and every updateBadgeState/updateRenderState call no-ops via its
88
+ // own _zConnectionBadge null-guard.
89
+ if (this._badgeEnabled()) {
90
+ const badgeElement = this._ensureChromeHost('zBifrostBadge', (el) => {
91
+ zVaFRoot.appendChild(el);
92
+ });
93
+ if (badgeElement) {
94
+ this.client._zConnectionBadge = badgeElement;
95
+ this.populateConnectionBadge();
96
+ this.logger.debug('[ZVaFManager] Badge host ready');
97
+ }
98
+ } else {
99
+ this.logger.debug('[ZVaFManager] Badge zHook disabled (zHooks.badge:false)');
100
+ }
101
+
102
+ // Client-side navigation (SPA link interception + popstate handler) is
103
+ // PAGE-LEVEL infrastructure — independent of whether this page renders a
104
+ // navbar. Enable it unconditionally here. Previously it was only wired from
105
+ // populateNavBar()'s navbar branches, so a zNavBar:false page loaded directly
106
+ // (hard reload) never registered the popstate listener → server-driven
107
+ // history.back() rewrote the URL without re-rendering. (populateNavBar still
108
+ // calls it too; enablement is idempotent.)
109
+ this.client._enableClientSideNavigation().catch(err => {
110
+ this.logger.error('[ZVaFManager] Failed to enable client-side navigation:', err);
111
+ });
112
+
113
+ this.logger.log('[ZVaFManager] All elements initialized under <zVaF> root');
114
+ }
115
+
116
+ /**
117
+ * Is the connection-badge zHook enabled? Default-on; opt out via
118
+ * zHooks: { badge: false }. (Core zHook — gated here, not loaded as a module.)
119
+ * @returns {boolean}
120
+ */
121
+ _badgeEnabled() {
122
+ const zHooks = this.options.zHooks || {};
123
+ return zHooks.badge !== false;
124
+ }
125
+
126
+ /**
127
+ * Ensure a runtime-owned chrome host element exists, returning it.
128
+ *
129
+ * Reuses an existing element if present (legacy templates); otherwise creates it
130
+ * and places it via `place(el)`. Hard-cut SSOT — <zNavBar>/<zBifrostBadge> are no
131
+ * longer required in app templates; the runtime creates them inside <zVaF>.
132
+ *
133
+ * @param {string} tagName - Custom element tag (zNavBar | zBifrostBadge)
134
+ * @param {(el: HTMLElement) => void} place - Inserts the freshly created element
135
+ * @returns {HTMLElement}
136
+ */
137
+ _ensureChromeHost(tagName, place) {
138
+ let el = document.querySelector(tagName);
139
+ if (el) return el;
140
+ el = document.createElement(tagName);
141
+ place(el);
142
+ this.logger.debug(`[ZVaFManager] Created runtime chrome host <${tagName}>`);
143
+ return el;
144
+ }
145
+
146
+ /**
147
+ * Populate connection badge content (v1.6.0: Simplified - element exists, just set content)
148
+ */
149
+ populateConnectionBadge() {
150
+ if (!this.client._zConnectionBadge) {
151
+ return;
152
+ }
153
+
154
+ // Set initial badge content (will be updated by connection hooks)
155
+ this.client._zConnectionBadge.className = 'zConnection zBadge zBadge-connection zBadge-pending';
156
+ this.client._zConnectionBadge.innerHTML = `
157
+ <svg class="zIcon zIcon-sm zBadge-dot" aria-hidden="true">
158
+ <use xlink:href="#icon-circle-fill"></use>
159
+ </svg>
160
+ <span class="zBadge-text">Connecting...</span>
161
+ `;
162
+
163
+ this.logger.log('[ConnectionBadge] Badge populated with initial state');
164
+ }
165
+
166
+ /**
167
+ * Update badge state (v1.6.0: Helper method called from hooks)
168
+ * @param {string} state - 'connecting', 'connected', 'disconnected', 'error'
169
+ */
170
+ updateBadgeState(state) {
171
+ if (!this.client._zConnectionBadge) {
172
+ // Badge is opt-in (zHooks.badge). When disabled the element is never
173
+ // created — that's expected, not an error. Stay quiet (matches
174
+ // updateRenderState's silent no-op) so it doesn't spam the console.
175
+ this.logger.debug('[ConnectionBadge] No badge element (badge disabled) — skipping update');
176
+ return;
177
+ }
178
+
179
+ const badge = this.client._zConnectionBadge;
180
+ const badgeText = badge.querySelector('.zBadge-text');
181
+
182
+ if (!badgeText) {
183
+ this.logger.warn('[ConnectionBadge] Cannot update - badge text element not found');
184
+ return;
185
+ }
186
+
187
+ this.logger.debug(`[ConnectionBadge] Updating badge to: ${state}`);
188
+
189
+ // Remove all state classes
190
+ badge.classList.remove('zBadge-pending', 'zBadge-success', 'zBadge-error');
191
+
192
+ // Apply new state
193
+ switch (state) {
194
+ case 'connected':
195
+ badge.classList.add('zBadge-success');
196
+ badgeText.textContent = 'Connected';
197
+ this.logger.log('[ConnectionBadge] Connected');
198
+ break;
199
+ case 'disconnected':
200
+ badge.classList.add('zBadge-pending');
201
+ badgeText.textContent = 'Disconnected';
202
+ this.logger.debug('[ConnectionBadge] Disconnected');
203
+ break;
204
+ case 'error':
205
+ badge.classList.add('zBadge-error');
206
+ badgeText.textContent = 'Error';
207
+ this.logger.debug('[ConnectionBadge] Error');
208
+ break;
209
+ case 'connecting':
210
+ default:
211
+ badge.classList.add('zBadge-pending');
212
+ badgeText.textContent = 'Connecting...';
213
+ this.logger.debug('[ConnectionBadge] Connecting');
214
+ break;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Update the badge with bifrost RENDER status (distinct from connection state).
220
+ *
221
+ * This is the zOS↔user "page is painting" contract: as the runtime streams a
222
+ * page in, the badge reads "Rendering k/N", then snaps back to the connected
223
+ * state when the last section lands. It reuses the same pending/success chip —
224
+ * no new chrome, no layout impact.
225
+ *
226
+ * @param {Object} opts
227
+ * @param {number} [opts.current] - Sections painted so far
228
+ * @param {number} [opts.total] - Total sections in this render
229
+ * @param {boolean} [opts.done] - Render finished → restore connected state
230
+ */
231
+ updateRenderState({ current = 0, total = 0, done = false } = {}) {
232
+ if (done) {
233
+ this.updateBadgeState('connected');
234
+ return;
235
+ }
236
+ const badge = this.client._zConnectionBadge;
237
+ if (!badge) return;
238
+ const badgeText = badge.querySelector('.zBadge-text');
239
+ if (!badgeText) return;
240
+
241
+ badge.classList.remove('zBadge-success', 'zBadge-error');
242
+ badge.classList.add('zBadge-pending');
243
+ badgeText.textContent = total > 0 ? `Rendering ${current}/${total}` : 'Rendering…';
244
+ }
245
+
246
+ /**
247
+ * Populate navbar from embedded config.
248
+ * 3A: Prefers server-built nav_html; falls back to array-based builder.
249
+ */
250
+ async populateNavBar() {
251
+ if (!this.client._zNavBarElement) return;
252
+
253
+ try {
254
+ const navHtml = this.zuiConfig?.nav_html;
255
+
256
+ if (navHtml) {
257
+ // 3A path: Python sent pre-built HTML — inject + wire events
258
+ this.client._zNavBarElement.innerHTML = navHtml;
259
+ NavBarBuilder.wireNavBarEvents(
260
+ this.client._zNavBarElement.firstElementChild,
261
+ this.client,
262
+ this.logger
263
+ );
264
+ this.logger.log('[NavBar] NavBar populated from server HTML (3A)');
265
+ } else if (this.zuiConfig?.zNavBar) {
266
+ // Legacy path: build from items array
267
+ const navElement = await this.client._renderMetaNavBarHTML(this.zuiConfig.zNavBar);
268
+ this.client._zNavBarElement.innerHTML = '';
269
+ if (navElement) {
270
+ this.client._zNavBarElement.appendChild(navElement);
271
+ this.logger.log('[NavBar] NavBar populated from embedded config (DOM element):', this.zuiConfig.zNavBar);
272
+ } else {
273
+ this.logger.warn('[NavBar] renderMetaNavBarHTML returned null');
274
+ }
275
+ } else {
276
+ this.logger.warn('[NavBar] No nav_html or zNavBar in embedded zuiConfig');
277
+ return;
278
+ }
279
+
280
+ await this.client._enableClientSideNavigation();
281
+ } catch (error) {
282
+ this.logger.error('[NavBar] Failed to populate:', error);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Re-populate navbar from connection_info nav_html (3A) or API fallback.
288
+ * Called after auth state changes / reconnect.
289
+ */
290
+ async fetchAndPopulateNavBar(navHtmlFromServer = null) {
291
+ if (!this.client._zNavBarElement) return;
292
+
293
+ try {
294
+ if (navHtmlFromServer) {
295
+ // 3A path: server pushed updated HTML via connection_info
296
+ this.client._zNavBarElement.innerHTML = navHtmlFromServer;
297
+ NavBarBuilder.wireNavBarEvents(
298
+ this.client._zNavBarElement.firstElementChild,
299
+ this.client,
300
+ this.logger
301
+ );
302
+ this.logger.log('[NavBar] NavBar updated from connection_info nav_html (3A)');
303
+ await this.client._enableClientSideNavigation();
304
+ return;
305
+ }
306
+
307
+ // Client-side bounce-back / refresh has no new server round-trip, so reuse
308
+ // the RBAC-filtered nav_html the server embedded in the page head
309
+ // (zui-config). This is the SSOT: the server resolves the navbar and ships
310
+ // it inline (and via connection_info / route-config). The legacy
311
+ // /api/zui/config endpoint was removed — the client no longer re-fetches a
312
+ // parallel copy of server-owned config.
313
+ const embeddedNav = this.zuiConfig?.nav_html || this.client?.zuiConfig?.nav_html;
314
+ if (embeddedNav) {
315
+ this.client._zNavBarElement.innerHTML = embeddedNav;
316
+ NavBarBuilder.wireNavBarEvents(
317
+ this.client._zNavBarElement.firstElementChild,
318
+ this.client,
319
+ this.logger
320
+ );
321
+ this.logger.log('[NavBar] NavBar refreshed from embedded zui-config nav_html');
322
+ await this.client._enableClientSideNavigation();
323
+ return;
324
+ }
325
+
326
+ this.logger.warn('[NavBar] No embedded nav_html — nothing to refresh');
327
+ } catch (error) {
328
+ this.logger.error('[NavBar] Failed to populate:', error);
329
+ }
330
+ }
331
+ }
332
+
333
+ export default ZVaFManager;
334
+
@@ -0,0 +1,12 @@
1
+ /**
2
+ * L3_Abstraction - High-Level Abstractions Layer
3
+ *
4
+ * Coordinates L2 handlers, provides unified interfaces.
5
+ * Depends on: L1_Foundation, L2_Handling
6
+ * Provides: Orchestration, Rendering, Session management
7
+ */
8
+
9
+ // Export subdirectories (will be populated in Step 4)
10
+ export * from './orchestrator/orchestrator.js';
11
+ export * from './renderer/renderer.js';
12
+ export * from './session/session.js';
@@ -0,0 +1,101 @@
1
+ /**
2
+ * L3_Abstraction/orchestrator/container_unwrapper.js
3
+ *
4
+ * Container Unwrapping Logic
5
+ *
6
+ * Prevents double-nesting and unnecessary wrapper divs by detecting
7
+ * when a container and its child have identical classes or when a
8
+ * container has no styling purpose.
9
+ *
10
+ * Unwrapping rules:
11
+ * 1. If container and element have identical classes → unwrap
12
+ * 2. If container has no classes AND no styles → unwrap
13
+ * 3. If element has all container classes (superset) → unwrap
14
+ * 4. Otherwise → keep wrapper
15
+ *
16
+ * Extracted from zdisplay_orchestrator.js (Phase 4.4c)
17
+ */
18
+
19
+ /**
20
+ * ContainerUnwrapper - Detects and handles unnecessary wrapper divs
21
+ */
22
+ export class ContainerUnwrapper {
23
+ constructor(logger) {
24
+ this.logger = logger;
25
+ }
26
+
27
+ /**
28
+ * Check if container should be unwrapped (element appended directly to parent)
29
+ * @param {HTMLElement} container - The wrapper container
30
+ * @param {HTMLElement} element - The child element
31
+ * @returns {boolean} True if should unwrap
32
+ */
33
+ shouldUnwrap(container, element) {
34
+ // Never unwrap comment nodes (delegated elements)
35
+ if (element.nodeType === Node.COMMENT_NODE) {
36
+ return false;
37
+ }
38
+
39
+ const containerClasses = container.className ? container.className.split(' ').filter(c => c.trim()) : [];
40
+ const elementClasses = element.className ? element.className.split(' ').filter(c => c.trim()) : [];
41
+ const hasAllContainerClasses = containerClasses.length > 0 && containerClasses.every(cls => elementClasses.includes(cls));
42
+
43
+ // Case 1: Container and element have identical classes (or element has all container classes)
44
+ if (container.className && element.className && (container.className === element.className || hasAllContainerClasses)) {
45
+ this.logger.debug(`[ContainerUnwrapper] Should unwrap: element has same/superset classes`);
46
+ return true;
47
+ }
48
+
49
+ // Case 2: Container has no classes AND no styles (no styling purpose)
50
+ if ((!container.className || container.className === '') && !container.getAttribute('style')) {
51
+ this.logger.debug(`[ContainerUnwrapper] Should unwrap: container has no styling`);
52
+ return true;
53
+ }
54
+
55
+ // Case 3: Keep wrapper
56
+ this.logger.debug(`[ContainerUnwrapper] Keep wrapper (classes: ${container.className || 'none'})`);
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Unwrap a container by transferring metadata to element and returning element
62
+ * @param {HTMLElement} container - The wrapper container
63
+ * @param {HTMLElement} element - The child element
64
+ * @param {string} key - The data-zkey value
65
+ * @returns {HTMLElement} The unwrapped element
66
+ */
67
+ unwrap(container, element, key) {
68
+ // Transfer data-zkey and id to the element
69
+ element.setAttribute('data-zkey', key);
70
+ if (!element.id) {
71
+ element.setAttribute('id', key);
72
+ }
73
+
74
+ this.logger.debug(`[ContainerUnwrapper] Unwrapped ${key}: appending directly to parent`);
75
+ return element;
76
+ }
77
+
78
+ /**
79
+ * Process element unwrapping and return result
80
+ * Returns { shouldAppendContainer: boolean, elementToAppend: HTMLElement }
81
+ * @param {HTMLElement} container - The wrapper container
82
+ * @param {HTMLElement} element - The child element
83
+ * @param {string} key - The data-zkey value
84
+ * @returns {{shouldAppendContainer: boolean, elementToAppend: HTMLElement}}
85
+ */
86
+ processUnwrapping(container, element, key) {
87
+ if (this.shouldUnwrap(container, element)) {
88
+ return {
89
+ shouldAppendContainer: false,
90
+ elementToAppend: this.unwrap(container, element, key)
91
+ };
92
+ } else {
93
+ this.logger.debug(`[ContainerUnwrapper] Keeping wrapper for ${key} (classes: ${container.className || 'none'})`);
94
+ container.appendChild(element);
95
+ return {
96
+ shouldAppendContainer: true,
97
+ elementToAppend: container
98
+ };
99
+ }
100
+ }
101
+ }