@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.
- package/L1_Foundation/L1_Foundation.js +13 -0
- package/L1_Foundation/bootstrap/bootstrap.js +11 -0
- package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
- package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
- package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
- package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
- package/L1_Foundation/bootstrap/module_registry.js +102 -0
- package/L1_Foundation/bootstrap/prism_loader.js +164 -0
- package/L1_Foundation/config/client_config.js +110 -0
- package/L1_Foundation/config/config.js +7 -0
- package/L1_Foundation/connection/connection.js +8 -0
- package/L1_Foundation/connection/websocket_connection.js +122 -0
- package/L1_Foundation/constants/bifrost_constants.js +284 -0
- package/L1_Foundation/constants/constants.js +7 -0
- package/L1_Foundation/logger/logger.js +10 -0
- package/L2_Handling/L2_Handling.js +15 -0
- package/L2_Handling/cache/cache.js +22 -0
- package/L2_Handling/cache/cache_constants.js +69 -0
- package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
- package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
- package/L2_Handling/cache/orchestration/orchestration.js +12 -0
- package/L2_Handling/cache/storage/session_manager.js +289 -0
- package/L2_Handling/cache/storage/storage.js +10 -0
- package/L2_Handling/cache/storage/storage_manager.js +590 -0
- package/L2_Handling/display/composite/composite.js +13 -0
- package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
- package/L2_Handling/display/composite/swiper_renderer.js +564 -0
- package/L2_Handling/display/composite/terminal_renderer.js +922 -0
- package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
- package/L2_Handling/display/display.js +30 -0
- package/L2_Handling/display/feedback/feedback.js +11 -0
- package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
- package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
- package/L2_Handling/display/inputs/button_renderer.js +634 -0
- package/L2_Handling/display/inputs/form_renderer.js +583 -0
- package/L2_Handling/display/inputs/input_renderer.js +658 -0
- package/L2_Handling/display/inputs/inputs.js +12 -0
- package/L2_Handling/display/navigation/menu_renderer.js +206 -0
- package/L2_Handling/display/navigation/navigation.js +11 -0
- package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
- package/L2_Handling/display/orchestration/orchestration.js +11 -0
- package/L2_Handling/display/orchestration/renderer.js +430 -0
- package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
- package/L2_Handling/display/outputs/alert_renderer.js +161 -0
- package/L2_Handling/display/outputs/audio_renderer.js +94 -0
- package/L2_Handling/display/outputs/card_renderer.js +229 -0
- package/L2_Handling/display/outputs/code_renderer.js +66 -0
- package/L2_Handling/display/outputs/dl_renderer.js +131 -0
- package/L2_Handling/display/outputs/header_renderer.js +162 -0
- package/L2_Handling/display/outputs/icon_renderer.js +107 -0
- package/L2_Handling/display/outputs/image_renderer.js +145 -0
- package/L2_Handling/display/outputs/list_renderer.js +190 -0
- package/L2_Handling/display/outputs/outputs.js +19 -0
- package/L2_Handling/display/outputs/table_renderer.js +765 -0
- package/L2_Handling/display/outputs/text_renderer.js +818 -0
- package/L2_Handling/display/outputs/typography_renderer.js +293 -0
- package/L2_Handling/display/outputs/video_renderer.js +116 -0
- package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
- package/L2_Handling/display/primitives/form_primitives.js +526 -0
- package/L2_Handling/display/primitives/generic_containers.js +109 -0
- package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
- package/L2_Handling/display/primitives/link_primitives.js +552 -0
- package/L2_Handling/display/primitives/lists_primitives.js +262 -0
- package/L2_Handling/display/primitives/media_primitives.js +383 -0
- package/L2_Handling/display/primitives/primitives.js +19 -0
- package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
- package/L2_Handling/display/primitives/table_primitives.js +528 -0
- package/L2_Handling/display/primitives/typography_primitives.js +175 -0
- package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
- package/L2_Handling/display/specialized/specialized.js +10 -0
- package/L2_Handling/hooks/hooks.js +9 -0
- package/L2_Handling/hooks/menu_integration.js +57 -0
- package/L2_Handling/hooks/widget_hook_manager.js +292 -0
- package/L2_Handling/message/message.js +8 -0
- package/L2_Handling/message/message_handler.js +701 -0
- package/L2_Handling/navigation/navigation.js +8 -0
- package/L2_Handling/navigation/navigation_manager.js +403 -0
- package/L2_Handling/zhooks/features/cache_live.js +287 -0
- package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
- package/L2_Handling/zhooks/zhooks_manager.js +65 -0
- package/L2_Handling/zvaf/zvaf.js +8 -0
- package/L2_Handling/zvaf/zvaf_manager.js +334 -0
- package/L3_Abstraction/L3_Abstraction.js +12 -0
- package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
- package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
- package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
- package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
- package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
- package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
- package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
- package/L3_Abstraction/renderer/renderer.js +1 -0
- package/L3_Abstraction/session/session.js +1 -0
- package/L4_Orchestration/L4_Orchestration.js +11 -0
- package/L4_Orchestration/client/client.js +1 -0
- package/L4_Orchestration/facade/facade.js +9 -0
- package/L4_Orchestration/facade/manager_registry.js +118 -0
- package/L4_Orchestration/facade/renderer_registry.js +274 -0
- package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
- package/L4_Orchestration/lifecycle/initializer.js +135 -0
- package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
- package/L4_Orchestration/rendering/facade.js +94 -0
- package/L4_Orchestration/rendering/rendering.js +7 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bifrost_client.js +204 -0
- package/bifrost_core.js +1686 -0
- package/docs/ARCHITECTURE.md +111 -0
- package/docs/PROTOCOL.md +106 -0
- package/docs/RENDERERS.md +101 -0
- package/docs/SECURITY.md +92 -0
- package/package.json +24 -0
- package/syntax/prism-zconfig.js +41 -0
- package/syntax/prism-zenv.js +69 -0
- package/syntax/prism-zolo-theme.css +288 -0
- package/syntax/prism-zolo.js +380 -0
- package/syntax/prism-zschema.js +38 -0
- package/syntax/prism-zspark.js +25 -0
- package/syntax/prism-zui.js +68 -0
- package/zSys/accessibility/accessibility.js +10 -0
- package/zSys/accessibility/emoji_accessibility.js +173 -0
- package/zSys/dom/block_utils.js +122 -0
- package/zSys/dom/container_utils.js +370 -0
- package/zSys/dom/dom.js +13 -0
- package/zSys/dom/dom_utils.js +328 -0
- package/zSys/dom/encoding_utils.js +117 -0
- package/zSys/dom/style_utils.js +71 -0
- package/zSys/errors/error_display.js +299 -0
- package/zSys/errors/errors.js +10 -0
- package/zSys/theme/color_utils.js +274 -0
- package/zSys/theme/dark_mode_utils.js +272 -0
- package/zSys/theme/size_utils.js +256 -0
- package/zSys/theme/spacing_utils.js +405 -0
- package/zSys/theme/theme.js +14 -0
- package/zSys/theme/zbase.css +1735 -0
- package/zSys/theme/zbase_inject.js +161 -0
- package/zSys/theme/ztheme_utils.js +305 -0
- package/zSys/validation/error_boundary.js +201 -0
- package/zSys/validation/validation.js +11 -0
- package/zSys/validation/validation_utils.js +238 -0
- package/zSys/zSys.js +14 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4_Orchestration/facade/renderer_registry.js
|
|
3
|
+
*
|
|
4
|
+
* Renderer Registry - Centralized Lazy Loading for All Renderers
|
|
5
|
+
*
|
|
6
|
+
* Consolidates 16 individual _ensure*Renderer() methods from bifrost_client.js
|
|
7
|
+
* into a single registry-based loader.
|
|
8
|
+
*
|
|
9
|
+
* Extracted from bifrost_client.js (Task 0, Step 1.2)
|
|
10
|
+
*
|
|
11
|
+
* @module facade/renderer_registry
|
|
12
|
+
* @layer L4 (Orchestration)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renderer Registry - Maps renderer types to their module paths and classes
|
|
17
|
+
*
|
|
18
|
+
* TODO: Tiered initialization for publish-time performance
|
|
19
|
+
* ──────────────────────────────────────────────────────────
|
|
20
|
+
* Add a `priority` field to each entry:
|
|
21
|
+
* - 'critical' → load before WebSocket connects (blocks first paint)
|
|
22
|
+
* - 'deferred' → load after first chunk renders (requestIdleCallback / post-paint hook)
|
|
23
|
+
*
|
|
24
|
+
* Critical tier (must exist before first chunk renders):
|
|
25
|
+
* typography, text, header (header_renderer if separate), list
|
|
26
|
+
*
|
|
27
|
+
* Deferred tier (never in a typical first chunk):
|
|
28
|
+
* code, card, button, table, dl, image, icon, navigation,
|
|
29
|
+
* dashboard, swiper, terminal, spinner, progressBar, form, menu
|
|
30
|
+
*
|
|
31
|
+
* Implementation sketch:
|
|
32
|
+
* 1. Add `priority: 'critical' | 'deferred'` to each entry below
|
|
33
|
+
* 2. In asset_loader.js (or initializer.js), call
|
|
34
|
+
* rendererRegistry.preloadTier('critical') before WS connect
|
|
35
|
+
* 3. After first-paint signal (first WebSocket chunk ACK), call
|
|
36
|
+
* rendererRegistry.preloadTier('deferred') via requestIdleCallback
|
|
37
|
+
* 4. ensureRenderer() stays unchanged — cache hit on any pre-loaded renderer
|
|
38
|
+
*
|
|
39
|
+
* Expected gain: time-to-interactive drops from ~40 module fetches to ~4-5
|
|
40
|
+
* before first content paint, remainder loads invisibly in background.
|
|
41
|
+
*/
|
|
42
|
+
export const RENDERER_REGISTRY = {
|
|
43
|
+
// Outputs - Typography & Text
|
|
44
|
+
typography: {
|
|
45
|
+
path: 'L2_Handling/display/outputs/typography_renderer.js',
|
|
46
|
+
className: 'TypographyRenderer',
|
|
47
|
+
isDefault: true,
|
|
48
|
+
passClient: false
|
|
49
|
+
},
|
|
50
|
+
text: {
|
|
51
|
+
path: 'L2_Handling/display/outputs/text_renderer.js',
|
|
52
|
+
className: 'TextRenderer',
|
|
53
|
+
isDefault: true,
|
|
54
|
+
passClient: false
|
|
55
|
+
},
|
|
56
|
+
code: {
|
|
57
|
+
path: 'L2_Handling/display/outputs/code_renderer.js',
|
|
58
|
+
className: 'CodeRenderer',
|
|
59
|
+
isDefault: true,
|
|
60
|
+
passClient: false
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Outputs - Cards & Buttons
|
|
64
|
+
card: {
|
|
65
|
+
path: 'L2_Handling/display/outputs/card_renderer.js',
|
|
66
|
+
className: 'CardRenderer',
|
|
67
|
+
isDefault: true,
|
|
68
|
+
passClient: false
|
|
69
|
+
},
|
|
70
|
+
button: {
|
|
71
|
+
path: 'L2_Handling/display/inputs/button_renderer.js',
|
|
72
|
+
className: 'ButtonRenderer',
|
|
73
|
+
isDefault: true,
|
|
74
|
+
passClient: true // ButtonRenderer needs client for event handling
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Outputs - Data Display
|
|
78
|
+
table: {
|
|
79
|
+
path: 'L2_Handling/display/outputs/table_renderer.js',
|
|
80
|
+
className: 'TableRenderer',
|
|
81
|
+
isDefault: true,
|
|
82
|
+
passClient: false
|
|
83
|
+
},
|
|
84
|
+
list: {
|
|
85
|
+
path: 'L2_Handling/display/outputs/list_renderer.js',
|
|
86
|
+
className: 'ListRenderer',
|
|
87
|
+
isDefault: true,
|
|
88
|
+
passClient: true // ListRenderer needs client for nested rendering
|
|
89
|
+
},
|
|
90
|
+
dl: {
|
|
91
|
+
path: 'L2_Handling/display/outputs/dl_renderer.js',
|
|
92
|
+
className: 'DLRenderer',
|
|
93
|
+
isDefault: false,
|
|
94
|
+
passClient: false,
|
|
95
|
+
useModuleRegistry: true // Uses MODULE_REGISTRY for loading
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Outputs - Media
|
|
99
|
+
image: {
|
|
100
|
+
path: 'L2_Handling/display/outputs/image_renderer.js',
|
|
101
|
+
className: 'ImageRenderer',
|
|
102
|
+
isDefault: true,
|
|
103
|
+
passClient: false
|
|
104
|
+
},
|
|
105
|
+
video: {
|
|
106
|
+
path: 'L2_Handling/display/outputs/video_renderer.js',
|
|
107
|
+
className: 'VideoRenderer',
|
|
108
|
+
isDefault: true,
|
|
109
|
+
passClient: false
|
|
110
|
+
},
|
|
111
|
+
audio: {
|
|
112
|
+
path: 'L2_Handling/display/outputs/audio_renderer.js',
|
|
113
|
+
className: 'AudioRenderer',
|
|
114
|
+
isDefault: true,
|
|
115
|
+
passClient: false
|
|
116
|
+
},
|
|
117
|
+
icon: {
|
|
118
|
+
path: 'L2_Handling/display/outputs/icon_renderer.js',
|
|
119
|
+
className: 'IconRenderer',
|
|
120
|
+
isDefault: true,
|
|
121
|
+
passClient: false
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// Outputs - Navigation
|
|
125
|
+
navigation: {
|
|
126
|
+
path: 'L2_Handling/display/outputs/navigation_renderer.js',
|
|
127
|
+
className: 'NavigationRenderer',
|
|
128
|
+
isDefault: false,
|
|
129
|
+
passClient: true, // NavigationRenderer needs client for link primitives
|
|
130
|
+
useModuleRegistry: true
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Composite - Complex Components
|
|
134
|
+
dashboard: {
|
|
135
|
+
path: 'L2_Handling/display/composite/dashboard_renderer.js',
|
|
136
|
+
className: 'DashboardRenderer',
|
|
137
|
+
isDefault: true,
|
|
138
|
+
passClient: true // DashboardRenderer needs client for nested rendering
|
|
139
|
+
},
|
|
140
|
+
swiper: {
|
|
141
|
+
path: 'L2_Handling/display/composite/swiper_renderer.js',
|
|
142
|
+
className: 'SwiperRenderer',
|
|
143
|
+
isDefault: true,
|
|
144
|
+
passClient: false
|
|
145
|
+
},
|
|
146
|
+
terminal: {
|
|
147
|
+
path: 'L2_Handling/display/composite/terminal_renderer.js',
|
|
148
|
+
className: 'TerminalRenderer',
|
|
149
|
+
isDefault: true,
|
|
150
|
+
passClient: true, // TerminalRenderer needs client for execution
|
|
151
|
+
exposeToWindow: true // Expose to window._TerminalRenderer for message handler
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// Feedback - UI State
|
|
155
|
+
spinner: {
|
|
156
|
+
path: 'L2_Handling/display/feedback/spinner_renderer.js',
|
|
157
|
+
className: 'SpinnerRenderer',
|
|
158
|
+
isDefault: true,
|
|
159
|
+
passClient: false
|
|
160
|
+
},
|
|
161
|
+
progressBar: {
|
|
162
|
+
path: 'L2_Handling/display/feedback/progressbar_renderer.js',
|
|
163
|
+
className: 'ProgressBarRenderer',
|
|
164
|
+
isDefault: true,
|
|
165
|
+
passClient: false
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Inputs - Forms
|
|
169
|
+
form: {
|
|
170
|
+
path: 'L2_Handling/display/inputs/form_renderer.js',
|
|
171
|
+
className: 'FormRenderer',
|
|
172
|
+
isDefault: false,
|
|
173
|
+
passClient: true, // FormRenderer needs client for form handling
|
|
174
|
+
useModuleRegistry: true
|
|
175
|
+
},
|
|
176
|
+
menu: {
|
|
177
|
+
path: 'L2_Handling/display/navigation/menu_renderer.js',
|
|
178
|
+
className: 'MenuRenderer',
|
|
179
|
+
isDefault: false,
|
|
180
|
+
passClient: true, // MenuRenderer needs client for menu interactions
|
|
181
|
+
useModuleRegistry: true
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* RendererRegistry - Centralized renderer loading and caching
|
|
187
|
+
*/
|
|
188
|
+
export class RendererRegistry {
|
|
189
|
+
constructor(client) {
|
|
190
|
+
this.client = client;
|
|
191
|
+
this.logger = client.logger;
|
|
192
|
+
this.baseUrl = client._baseUrl;
|
|
193
|
+
this.renderers = {}; // Cache for loaded renderers
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Ensure a renderer is loaded and cached
|
|
198
|
+
* @param {string} type - Renderer type (e.g., 'typography', 'button', 'table')
|
|
199
|
+
* @returns {Promise<Object>} Renderer instance
|
|
200
|
+
*/
|
|
201
|
+
async ensureRenderer(type) {
|
|
202
|
+
// Return cached renderer if already loaded
|
|
203
|
+
if (this.renderers[type]) {
|
|
204
|
+
return this.renderers[type];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Get renderer config from registry
|
|
208
|
+
const config = RENDERER_REGISTRY[type];
|
|
209
|
+
if (!config) {
|
|
210
|
+
throw new Error(`Unknown renderer type: ${type}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Load renderer module
|
|
214
|
+
let RendererClass;
|
|
215
|
+
if (config.useModuleRegistry) {
|
|
216
|
+
// Use client's _loadModule for MODULE_REGISTRY lookup
|
|
217
|
+
const module = await this.client._loadModule(type === 'dl' ? 'dl_renderer' :
|
|
218
|
+
type === 'navigation' ? 'navigation_renderer' :
|
|
219
|
+
type === 'form' ? 'form_renderer' :
|
|
220
|
+
type === 'menu' ? 'menu_renderer' : type);
|
|
221
|
+
RendererClass = module[config.className];
|
|
222
|
+
} else {
|
|
223
|
+
// Direct import
|
|
224
|
+
const fullPath = `${this.baseUrl}${config.path}`;
|
|
225
|
+
const module = await import(fullPath);
|
|
226
|
+
RendererClass = config.isDefault ? module.default : module[config.className];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Instantiate renderer
|
|
230
|
+
const args = config.passClient ? [this.logger, this.client] : [this.logger];
|
|
231
|
+
const renderer = new RendererClass(...args);
|
|
232
|
+
|
|
233
|
+
// Expose to window if needed (for TerminalRenderer)
|
|
234
|
+
if (config.exposeToWindow) {
|
|
235
|
+
window[`_${config.className}`] = RendererClass;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Cache and return
|
|
239
|
+
this.renderers[type] = renderer;
|
|
240
|
+
this.logger.debug(`${config.className} loaded via registry`);
|
|
241
|
+
return renderer;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get a cached renderer (throws if not loaded)
|
|
246
|
+
* @param {string} type - Renderer type
|
|
247
|
+
* @returns {Object} Renderer instance
|
|
248
|
+
*/
|
|
249
|
+
getRenderer(type) {
|
|
250
|
+
const renderer = this.renderers[type];
|
|
251
|
+
if (!renderer) {
|
|
252
|
+
throw new Error(`Renderer not loaded: ${type}`);
|
|
253
|
+
}
|
|
254
|
+
return renderer;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if a renderer is loaded
|
|
259
|
+
* @param {string} type - Renderer type
|
|
260
|
+
* @returns {boolean}
|
|
261
|
+
*/
|
|
262
|
+
hasRenderer(type) {
|
|
263
|
+
return !!this.renderers[type];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Preload multiple renderers in parallel
|
|
268
|
+
* @param {string[]} types - Array of renderer types
|
|
269
|
+
* @returns {Promise<void>}
|
|
270
|
+
*/
|
|
271
|
+
async preloadRenderers(types) {
|
|
272
|
+
await Promise.all(types.map(type => this.ensureRenderer(type)));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4_Orchestration/lifecycle/asset_loader.js
|
|
3
|
+
*
|
|
4
|
+
* Asset Loading Orchestrator
|
|
5
|
+
*
|
|
6
|
+
* Manages loading of external assets and libraries:
|
|
7
|
+
* - Prism.js syntax highlighting (core + languages + .zolo variants)
|
|
8
|
+
* - _zScripts from YAML metadata (plugin scripts)
|
|
9
|
+
*
|
|
10
|
+
* Extracted from bifrost_client.js (Phase 5.4)
|
|
11
|
+
*
|
|
12
|
+
* TODO: Tiered renderer initialization for publish-time performance
|
|
13
|
+
* ──────────────────────────────────────────────────────────────────
|
|
14
|
+
* This is the right place to orchestrate the two-phase load sequence:
|
|
15
|
+
*
|
|
16
|
+
* Phase 1 — before WebSocket connect (called from initializer.js):
|
|
17
|
+
* await client.rendererRegistry.preloadTier('critical')
|
|
18
|
+
* // loads: typography, text, list — ~4 files, unblocks first paint
|
|
19
|
+
*
|
|
20
|
+
* Phase 2 — after first chunk renders (post-paint, background):
|
|
21
|
+
* const afterFirstPaint = () => {
|
|
22
|
+
* requestIdleCallback(() => client.rendererRegistry.preloadTier('deferred'));
|
|
23
|
+
* };
|
|
24
|
+
* // hook into message_handler.js first-chunk-rendered signal, or
|
|
25
|
+
* // use a one-shot WebSocket message ACK as the trigger
|
|
26
|
+
*
|
|
27
|
+
* Prerequisite: add `priority: 'critical' | 'deferred'` to each entry
|
|
28
|
+
* in renderer_registry.js (see TODO there), then add preloadTier() to
|
|
29
|
+
* RendererRegistry that filters by priority and calls ensureRenderer().
|
|
30
|
+
*
|
|
31
|
+
* No changes to ensureRenderer() needed — cache hit on pre-loaded renderers
|
|
32
|
+
* means existing lazy-load call sites stay identical.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export class AssetLoader {
|
|
36
|
+
constructor(client) {
|
|
37
|
+
this.client = client;
|
|
38
|
+
this.logger = client.logger;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Load _zScripts from YAML metadata (plugin scripts)
|
|
43
|
+
* Resolves plugin references: &.plugin_name → /plugins/plugin_name.js
|
|
44
|
+
*/
|
|
45
|
+
loadZScripts() {
|
|
46
|
+
if (typeof document === 'undefined') {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract zScripts from zuiConfig.zMeta (renamed from _zScripts in v1.7.26)
|
|
51
|
+
const zScripts = this.client.zuiConfig?.zMeta?.zScripts || this.client.zuiConfig?.zMeta?._zScripts || [];
|
|
52
|
+
|
|
53
|
+
if (!Array.isArray(zScripts) || zScripts.length === 0) {
|
|
54
|
+
this.logger.debug('[AssetLoader] No zScripts found in YAML metadata');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.logger.debug('[AssetLoader] Loading %s _zScripts from YAML', zScripts.length);
|
|
59
|
+
|
|
60
|
+
zScripts.forEach(scriptRef => {
|
|
61
|
+
// Resolve plugin reference: &.plugin_name → /plugins/plugin_name.js
|
|
62
|
+
let scriptUrl = scriptRef;
|
|
63
|
+
if (scriptRef.startsWith('&.')) {
|
|
64
|
+
const pluginName = scriptRef.substring(2);
|
|
65
|
+
scriptUrl = `/plugins/${pluginName}.js`;
|
|
66
|
+
this.logger.debug('[AssetLoader] Resolving plugin: %s → %s', scriptRef, scriptUrl);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if script already loaded
|
|
70
|
+
if (!document.querySelector(`script[src="${scriptUrl}"]`)) {
|
|
71
|
+
const script = document.createElement('script');
|
|
72
|
+
script.src = scriptUrl;
|
|
73
|
+
script.async = true;
|
|
74
|
+
script.onload = () => {
|
|
75
|
+
this.logger.debug('[AssetLoader] Loaded _zScript: %s', scriptUrl);
|
|
76
|
+
};
|
|
77
|
+
script.onerror = () => {
|
|
78
|
+
this.logger.error('[AssetLoader] Failed to load _zScript: %s', scriptUrl);
|
|
79
|
+
};
|
|
80
|
+
document.head.appendChild(script);
|
|
81
|
+
} else {
|
|
82
|
+
this.logger.debug('[AssetLoader] _zScript already loaded: %s', scriptUrl);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Load Prism.js from CDN for syntax highlighting
|
|
89
|
+
* Complex sequential loading: core → components → .zolo languages
|
|
90
|
+
*/
|
|
91
|
+
loadPrismJS() {
|
|
92
|
+
if (typeof document === 'undefined') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.logger.debug('[AssetLoader] Loading Prism.js...');
|
|
97
|
+
|
|
98
|
+
const prismCDN = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0';
|
|
99
|
+
const prismTheme = `${prismCDN}/themes/prism-tomorrow.min.css`;
|
|
100
|
+
|
|
101
|
+
// Check if Prism CSS already loaded
|
|
102
|
+
if (!document.querySelector(`link[href="${prismTheme}"]`)) {
|
|
103
|
+
const link = document.createElement('link');
|
|
104
|
+
link.rel = 'stylesheet';
|
|
105
|
+
link.href = prismTheme;
|
|
106
|
+
document.head.appendChild(link);
|
|
107
|
+
this.logger.debug('[AssetLoader] Prism CSS loaded (prism-tomorrow)');
|
|
108
|
+
} else {
|
|
109
|
+
this.logger.debug('[AssetLoader] Prism CSS already loaded');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Load custom .zolo theme overrides — bundled WITH the client (syntax/),
|
|
113
|
+
// resolved relative to this module so the zlsp palette ships built-in.
|
|
114
|
+
const zoloTheme = new URL('../../syntax/prism-zolo-theme.css', import.meta.url).href;
|
|
115
|
+
if (!document.querySelector(`link[href="${zoloTheme}"]`)) {
|
|
116
|
+
const link = document.createElement('link');
|
|
117
|
+
link.rel = 'stylesheet';
|
|
118
|
+
link.href = zoloTheme;
|
|
119
|
+
document.head.appendChild(link);
|
|
120
|
+
this.logger.debug('[AssetLoader] Prism .zolo custom theme loaded');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Load Prism core + common languages
|
|
124
|
+
const scripts = [
|
|
125
|
+
{ src: `${prismCDN}/prism.min.js`, name: 'core' },
|
|
126
|
+
{ src: `${prismCDN}/components/prism-markup.min.js`, name: 'markup' },
|
|
127
|
+
{ src: `${prismCDN}/components/prism-css.min.js`, name: 'css' },
|
|
128
|
+
{ src: `${prismCDN}/components/prism-javascript.min.js`, name: 'javascript' },
|
|
129
|
+
{ src: `${prismCDN}/components/prism-python.min.js`, name: 'python' },
|
|
130
|
+
{ src: `${prismCDN}/components/prism-bash.min.js`, name: 'bash' },
|
|
131
|
+
{ src: `${prismCDN}/components/prism-yaml.min.js`, name: 'yaml' }
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
// Load scripts sequentially (Prism components depend on core)
|
|
135
|
+
const loadScript = (scriptInfo, index) => {
|
|
136
|
+
// Check if script already loaded
|
|
137
|
+
if (document.querySelector(`script[src="${scriptInfo.src}"]`)) {
|
|
138
|
+
// Already loaded, continue to next
|
|
139
|
+
this.logger.debug(`[AssetLoader] Prism ${scriptInfo.name} already loaded`);
|
|
140
|
+
if (index < scripts.length - 1) {
|
|
141
|
+
loadScript(scripts[index + 1], index + 1);
|
|
142
|
+
} else {
|
|
143
|
+
this.logger.debug('[AssetLoader] All Prism.js scripts already loaded');
|
|
144
|
+
this.loadPrismZolo();
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const script = document.createElement('script');
|
|
150
|
+
script.src = scriptInfo.src;
|
|
151
|
+
script.onload = () => {
|
|
152
|
+
// Load next script in sequence (silent)
|
|
153
|
+
if (index < scripts.length - 1) {
|
|
154
|
+
loadScript(scripts[index + 1], index + 1);
|
|
155
|
+
} else {
|
|
156
|
+
// Load custom .zolo language definition
|
|
157
|
+
this.loadPrismZolo();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
script.onerror = () => {
|
|
161
|
+
this.logger.warn(`[AssetLoader] Failed to load Prism ${scriptInfo.name}`);
|
|
162
|
+
};
|
|
163
|
+
document.head.appendChild(script);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Start loading chain
|
|
167
|
+
loadScript(scripts[0], 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Load custom .zolo language definitions for Prism.js
|
|
172
|
+
* Loads 6 .zolo variants: zolo, zspark, zui, zschema, zconfig, zenv
|
|
173
|
+
*/
|
|
174
|
+
loadPrismZolo() {
|
|
175
|
+
if (typeof document === 'undefined') {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.logger.debug('[AssetLoader] Loading custom .zolo languages...');
|
|
180
|
+
|
|
181
|
+
// Keep dependency order deterministic: extensions rely on base "zolo".
|
|
182
|
+
const zoloLanguages = ['zolo', 'zspark', 'zui', 'zschema', 'zconfig', 'zenv'];
|
|
183
|
+
const totalLanguages = zoloLanguages.length;
|
|
184
|
+
|
|
185
|
+
// zolo grammars ship WITH the client (syntax/), not the host app. Resolve
|
|
186
|
+
// them relative to this module so every zApp gets zolo highlighting for
|
|
187
|
+
// free — no per-app /static/js/prism-*.js copies required.
|
|
188
|
+
const syntaxBase = new URL('../../syntax/', import.meta.url).href;
|
|
189
|
+
|
|
190
|
+
const alreadyLoaded = zoloLanguages.every((lang) => window.Prism?.languages?.[lang]);
|
|
191
|
+
if (alreadyLoaded) {
|
|
192
|
+
this.logger.debug('[AssetLoader] Prism .zolo languages already loaded');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const finishLoad = () => {
|
|
197
|
+
this.logger.debug('[AssetLoader] Prism.js loaded (7 languages + %s .zolo variants)', totalLanguages);
|
|
198
|
+
|
|
199
|
+
// Rehighlight ALL code blocks rendered before grammars loaded — covers
|
|
200
|
+
// zolo variants AND stock languages (bash, python, …) from zMD fences.
|
|
201
|
+
if (window.Prism) {
|
|
202
|
+
const codeBlocks = document.querySelectorAll('pre code[class*="language-"]');
|
|
203
|
+
if (codeBlocks.length > 0) {
|
|
204
|
+
this.logger.debug(`[AssetLoader] Rehighlighting ${codeBlocks.length} code blocks`);
|
|
205
|
+
codeBlocks.forEach(block => {
|
|
206
|
+
Prism.highlightElement(block);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const loadLanguageSequentially = (index) => {
|
|
213
|
+
if (index >= totalLanguages) {
|
|
214
|
+
finishLoad();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const lang = zoloLanguages[index];
|
|
219
|
+
const path = `${syntaxBase}prism-${lang}.js`;
|
|
220
|
+
|
|
221
|
+
// If language is already registered, continue.
|
|
222
|
+
if (window.Prism?.languages?.[lang]) {
|
|
223
|
+
this.logger.debug(`[AssetLoader] Prism ${lang} already registered`);
|
|
224
|
+
loadLanguageSequentially(index + 1);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Reuse existing script tag if present but language not yet available.
|
|
229
|
+
const existingScript = document.querySelector(`script[src="${path}"]`);
|
|
230
|
+
if (existingScript) {
|
|
231
|
+
existingScript.addEventListener('load', () => {
|
|
232
|
+
this.logger.debug(`[AssetLoader] Loaded Prism language ${index + 1}/${totalLanguages}: ${path}`);
|
|
233
|
+
loadLanguageSequentially(index + 1);
|
|
234
|
+
}, { once: true });
|
|
235
|
+
existingScript.addEventListener('error', () => {
|
|
236
|
+
this.logger.error(`[AssetLoader] Failed to load Prism language: ${path}`);
|
|
237
|
+
}, { once: true });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const script = document.createElement('script');
|
|
242
|
+
script.src = path;
|
|
243
|
+
script.onload = () => {
|
|
244
|
+
this.logger.debug(`[AssetLoader] Loaded Prism language ${index + 1}/${totalLanguages}: ${path}`);
|
|
245
|
+
loadLanguageSequentially(index + 1);
|
|
246
|
+
};
|
|
247
|
+
script.onerror = () => {
|
|
248
|
+
this.logger.error(`[AssetLoader] Failed to load Prism language: ${path}`);
|
|
249
|
+
};
|
|
250
|
+
document.head.appendChild(script);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
loadLanguageSequentially(0);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4_Orchestration/lifecycle/initializer.js
|
|
3
|
+
*
|
|
4
|
+
* Initialization Orchestrator
|
|
5
|
+
*
|
|
6
|
+
* Coordinates the initialization sequence for BifrostClient:
|
|
7
|
+
* - zVaF elements (connection badge, navbar, content area)
|
|
8
|
+
* - Client-side navigation setup
|
|
9
|
+
* - Widget hooks registration
|
|
10
|
+
* - Cache hooks registration
|
|
11
|
+
*
|
|
12
|
+
* Extracted from bifrost_client.js (Phase 5.2)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export class Initializer {
|
|
16
|
+
constructor(client) {
|
|
17
|
+
this.client = client;
|
|
18
|
+
this.logger = client.logger;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register default widget hooks (backward compatibility)
|
|
23
|
+
* Widget hooks are now registered in WidgetHookManager
|
|
24
|
+
*/
|
|
25
|
+
registerDefaultWidgetHooks() {
|
|
26
|
+
// Widget hooks are now registered in the widget handler
|
|
27
|
+
// This method is kept for backward compatibility
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register cache-related hooks (onConnectionInfo, onDisconnected, onConnected)
|
|
32
|
+
*/
|
|
33
|
+
async registerCacheHooks() {
|
|
34
|
+
await this.client._ensureCacheManager();
|
|
35
|
+
return this.client.cacheManager.registerCacheHooks();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Disable all forms during offline mode
|
|
40
|
+
*/
|
|
41
|
+
async disableForms() {
|
|
42
|
+
await this.client._ensureCacheManager();
|
|
43
|
+
return this.client.cacheManager.disableForms();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Enable all forms when back online
|
|
48
|
+
*/
|
|
49
|
+
async enableForms() {
|
|
50
|
+
await this.client._ensureCacheManager();
|
|
51
|
+
return this.client.cacheManager.enableForms();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize zVaF elements (connection badges, navbar, content area)
|
|
56
|
+
* HTML structure (declared in zVaF.html):
|
|
57
|
+
* <zBifrostBadge></zBifrostBadge> ← Dynamic, always fresh
|
|
58
|
+
* <zNavBar></zNavBar> ← Dynamic, RBAC-aware
|
|
59
|
+
* <zVaF>...</zVaF> ← Cacheable content area
|
|
60
|
+
*/
|
|
61
|
+
async initZVaFElements() {
|
|
62
|
+
await this.client._ensureZVaFManager();
|
|
63
|
+
return this.client.zvafManager.initZVaFElements();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Populate connection badge content
|
|
68
|
+
*/
|
|
69
|
+
async populateConnectionBadge() {
|
|
70
|
+
await this.client._ensureZVaFManager();
|
|
71
|
+
return this.client.zvafManager.populateConnectionBadge();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Update badge state
|
|
76
|
+
* @param {string} state - 'connecting', 'connected', 'disconnected', 'error'
|
|
77
|
+
*/
|
|
78
|
+
async updateBadgeState(state) {
|
|
79
|
+
await this.client._ensureZVaFManager();
|
|
80
|
+
return this.client.zvafManager.updateBadgeState(state);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Update badge with bifrost render status (Rendering k/N → connected)
|
|
85
|
+
* @param {Object} opts - { current, total, done }
|
|
86
|
+
*/
|
|
87
|
+
async updateRenderState(opts) {
|
|
88
|
+
await this.client._ensureZVaFManager();
|
|
89
|
+
return this.client.zvafManager.updateRenderState(opts);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Populate navbar from embedded config
|
|
94
|
+
* Uses zuiConfig from server, fetches fresh on auth change
|
|
95
|
+
*/
|
|
96
|
+
async populateNavBar() {
|
|
97
|
+
await this.client._ensureZVaFManager();
|
|
98
|
+
return this.client.zvafManager.populateNavBar();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Fetch fresh navbar from API and populate (used after auth state changes)
|
|
103
|
+
*/
|
|
104
|
+
async fetchAndPopulateNavBar(navHtmlFromServer = null) {
|
|
105
|
+
await this.client._ensureZVaFManager();
|
|
106
|
+
return this.client.zvafManager.fetchAndPopulateNavBar(navHtmlFromServer);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Enable client-side navigation (SPA-style) for navbar links
|
|
111
|
+
* Intercepts clicks to prevent full page reloads and uses WebSocket instead
|
|
112
|
+
*/
|
|
113
|
+
async enableClientSideNavigation() {
|
|
114
|
+
await this.client._ensureNavigationManager();
|
|
115
|
+
return this.client.navigationManager.enableClientSideNavigation();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Navigate to a route via WebSocket (client-side navigation)
|
|
120
|
+
* @param {string} routePath - Path to navigate to (e.g., '/zAbout', '/zAccount')
|
|
121
|
+
* @param {Object} options - Navigation options
|
|
122
|
+
*/
|
|
123
|
+
async navigateToRoute(routePath, options = {}) {
|
|
124
|
+
await this.client._ensureNavigationManager();
|
|
125
|
+
return this.client.navigationManager.navigateToRoute(routePath, options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Freeze the page currently on screen into the trail (offline-browse).
|
|
130
|
+
*/
|
|
131
|
+
async snapshotCurrentPage() {
|
|
132
|
+
await this.client._ensureNavigationManager();
|
|
133
|
+
return this.client.navigationManager.snapshotCurrentPage();
|
|
134
|
+
}
|
|
135
|
+
}
|