@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,162 @@
1
+ /**
2
+ *
3
+ * Header Renderer - Semantic Headers (h1-h6)
4
+ *
5
+ *
6
+ * Renders header events from zCLI backend. Converts indent levels
7
+ * to semantic HTML header tags (h1-h6) with optional colors.
8
+ *
9
+ * @module rendering/header_renderer
10
+ * @layer 3
11
+ * @pattern Strategy (single event type)
12
+ *
13
+ * Philosophy:
14
+ * - "Terminal first" - headers structure all zCLI output
15
+ * - Pure rendering (no WebSocket, no state, no side effects)
16
+ * - Semantic HTML (indent → h1-h6)
17
+ * - Uses Layer 2 utilities exclusively (no inline logic)
18
+ *
19
+ * Dependencies:
20
+ * - Layer 2: dom_utils.js, ztheme_utils.js
21
+ *
22
+ * Exports:
23
+ * - HeaderRenderer: Class for rendering header events
24
+ *
25
+ * Example:
26
+ * ```javascript
27
+ * import { HeaderRenderer } from './header_renderer.js';
28
+ *
29
+ * const renderer = new HeaderRenderer(logger);
30
+ * renderer.render({
31
+ * label: 'Welcome to zCLI',
32
+ * color: 'primary',
33
+ * indent: 1 // h1
34
+ * }, 'zVaF');
35
+ * ```
36
+ */
37
+
38
+ // ─────────────────────────────────────────────────────────────────
39
+ // Imports
40
+ // ─────────────────────────────────────────────────────────────────
41
+
42
+ // Layer 2: Utilities
43
+ import { createElement } from '../../../zSys/dom/dom_utils.js';
44
+ import { getTextColorClass, indentToHeaderTag } from '../../../zSys/theme/ztheme_utils.js';
45
+ import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
46
+
47
+ //
48
+ // Header Renderer Class
49
+ //
50
+
51
+ /**
52
+ * HeaderRenderer - Renders semantic header events
53
+ *
54
+ * Handles the 'header' zDisplay event. Converts indent levels
55
+ * to semantic HTML header tags (h1-h6) for proper document structure.
56
+ *
57
+ * Indent Mapping:
58
+ * - indent 0: Reserved for special cases (flush/hero layouts) - NOT a header
59
+ * - indent 1 → h1 (main title)
60
+ * - indent 2 → h2 (section)
61
+ * - indent 3 → h3 (subsection)
62
+ * - indent 4 → h4
63
+ * - indent 5 → h5
64
+ * - indent 6 → h6
65
+ * - indent 7+ → h6 (capped at h6)
66
+ */
67
+ export class HeaderRenderer {
68
+ /**
69
+ * Create a HeaderRenderer instance
70
+ * @param {Object} logger - Logger instance for debugging
71
+ */
72
+ constructor(logger) {
73
+ this.logger = logger || console;
74
+ this.logger.debug('[HeaderRenderer] Initialized');
75
+
76
+ // Wrap render method with error boundary
77
+ const originalRender = this.render.bind(this);
78
+ this.render = withErrorBoundary(originalRender, {
79
+ component: 'HeaderRenderer',
80
+ logger: this.logger
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Render a header event
86
+ *
87
+ * @param {Object} data - Header event data
88
+ * @param {string} data.label - Header text (backend sends 'label' not 'content')
89
+ * @param {string} [data.color] - Header color (primary, secondary, info, success, warning, error)
90
+ * @param {number} [data.indent=1] - Indent level (1-6, where 1=h1, 6=h6)
91
+ * Note: indent 0 is reserved for special cases
92
+ * @param {string} [data.class] - Custom CSS class (optional)
93
+ * @param {string} zone - Target DOM element ID
94
+ * @returns {HTMLElement|null} Created header element or null if failed
95
+ *
96
+ * @example
97
+ * renderer.render({ label: 'Main Title', indent: 1 }, 'zVaF'); // h1
98
+ * renderer.render({ label: 'Section', indent: 2, color: 'primary' }, 'zVaF'); // h2
99
+ * renderer.render({ label: 'Subsection', indent: 3 }, 'zVaF'); // h3
100
+ */
101
+ render(data, zone) {
102
+ // Backend sends 'label' for headers (not 'content')
103
+ const { label, color, indent = 1, class: customClass } = data;
104
+
105
+ // Validate required parameters
106
+ if (!label) {
107
+ this.logger.error('[HeaderRenderer] [ERROR] Missing required parameter: label');
108
+ return null;
109
+ }
110
+
111
+ // Handle indent 0 as special case (reserved for flush/hero layouts)
112
+ if (indent === 0) {
113
+ this.logger.warn('[HeaderRenderer] [WARN] indent=0 is reserved for special cases, treating as indent=1 (h1)');
114
+ // Could also skip rendering or return null, but treating as h1 is safer
115
+ }
116
+
117
+ // Get target container
118
+ const container = document.getElementById(zone);
119
+ if (!container) {
120
+ this.logger.error(`[HeaderRenderer] [ERROR] Zone not found: ${zone}`);
121
+ return null;
122
+ }
123
+
124
+ // Convert indent to header tag (h1-h6) using Layer 2 utility
125
+ // This automatically clamps indent between 1-6
126
+ const headerTag = indentToHeaderTag(indent);
127
+
128
+ // Build CSS classes array
129
+ const classes = [];
130
+
131
+ // Add custom class if provided (from YAML)
132
+ if (customClass) {
133
+ classes.push(customClass);
134
+ }
135
+
136
+ // Add color class if provided (uses Layer 2 utility)
137
+ if (color) {
138
+ const colorClass = getTextColorClass(color);
139
+ if (colorClass) {
140
+ classes.push(colorClass);
141
+ }
142
+ }
143
+
144
+ // Create header element (using Layer 2 utility)
145
+ const header = createElement(headerTag, classes);
146
+ header.textContent = label; // Use textContent for XSS safety
147
+
148
+ // Append to container
149
+ container.appendChild(header);
150
+
151
+ // Log success
152
+ this.logger.log(`[HeaderRenderer] Rendered ${headerTag} (${label.length} chars, indent: ${indent})`);
153
+
154
+ return header;
155
+ }
156
+ }
157
+
158
+ //
159
+ // Default Export
160
+ //
161
+ export default HeaderRenderer;
162
+
@@ -0,0 +1,107 @@
1
+ /**
2
+ * IconRenderer - Bootstrap Icons for Bifrost
3
+ *
4
+ * Renders Bootstrap Icons in web mode with support for:
5
+ * - Icon name (with or without 'bi-' prefix)
6
+ * - SSOT semantic color value (primary, warning, …) — resolved via getTextColorClass
7
+ * - Additional CSS classes (_zClass) — also the channel for sizing
8
+ *
9
+ * Features:
10
+ * - Clean HTML generation
11
+ * - Proper class composition
12
+ * - Graceful fallback for missing icons
13
+ * - Accessible by default: role="img" + aria-label derived from the icon name
14
+ * (or an explicit alt_text / aria_label override)
15
+ *
16
+ * Author: zOS Framework
17
+ * Version: 1.0.0
18
+ * Date: 2026-03-24
19
+ */
20
+
21
+ import { getTextColorClass } from '../../../zSys/theme/ztheme_utils.js';
22
+ import { convertStyleToString } from '../../../zSys/dom/style_utils.js';
23
+
24
+ export default class IconRenderer {
25
+ /**
26
+ * @param {Object} logger - Logger instance
27
+ */
28
+ constructor(logger) {
29
+ this.logger = logger;
30
+ }
31
+
32
+ /**
33
+ * Render Bootstrap Icon
34
+ * @param {Object} data - Icon configuration
35
+ * @param {string} data.name - Icon name (e.g., "tools", "bi-tools")
36
+ * @param {string} [data.color] - SSOT semantic color value (e.g., "primary", "warning")
37
+ * @param {string} [data._zClass] - Additional CSS classes (also sizing)
38
+ * @param {string|Object} [data._zStyle] - SSOT inline-style escape hatch
39
+ * @param {HTMLElement} [targetElement] - Optional parent to append into
40
+ * @returns {HTMLElement} The rendered node (bare <i>, or a styled <span> wrapper)
41
+ */
42
+ render(data, targetElement) {
43
+ if (!data || !data.name) {
44
+ this.logger.warn('[IconRenderer] Missing icon name');
45
+ return null;
46
+ }
47
+
48
+ // Strip 'bi-' prefix if present
49
+ const cleanName = data.name.replace(/^bi-/, '');
50
+
51
+ // Create icon element
52
+ const icon = document.createElement('i');
53
+ icon.className = `bi bi-${cleanName}`;
54
+
55
+ // Accessibility — a bare <i> is silent to screen readers. Honour the page's
56
+ // "accessible by default" promise: give every icon role="img" and a readable
57
+ // aria-label. An explicit alt_text/aria_label wins; otherwise humanize the
58
+ // bi-* name (bi-heart-fill → "heart fill"), mirroring the terminal's
59
+ // bracketed [description] fallback.
60
+ const ariaLabel = (data.alt_text || data.aria_label || cleanName.replace(/-/g, ' ')).trim();
61
+ if (ariaLabel) {
62
+ icon.setAttribute('role', 'img');
63
+ icon.setAttribute('aria-label', ariaLabel);
64
+ }
65
+
66
+ // Only wrap when the icon carries its OWN styling (colour / classes / inline
67
+ // style). A bare icon returns the raw <i> so the container-unwrapper can
68
+ // collapse any redundant parent frame instead of nesting a second box.
69
+ let node = icon;
70
+ if (data.color || data._zClass || data._zStyle) {
71
+ const wrapper = document.createElement('span');
72
+
73
+ // Build class list. color is an SSOT semantic value (primary, warning, …) —
74
+ // resolved through the same mapping zText/zH use, never a raw class.
75
+ const classes = [];
76
+ if (data.color) classes.push(getTextColorClass(data.color));
77
+ if (data._zClass) {
78
+ // Handle _zClass as string or array
79
+ if (Array.isArray(data._zClass)) {
80
+ classes.push(...data._zClass);
81
+ } else {
82
+ classes.push(...data._zClass.split(' ').filter(c => c));
83
+ }
84
+ }
85
+
86
+ if (classes.length > 0) {
87
+ wrapper.className = classes.join(' ');
88
+ }
89
+
90
+ // _zStyle — the SSOT escape hatch, same as every other event.
91
+ if (data._zStyle) {
92
+ const cssString = convertStyleToString(data._zStyle, this.logger);
93
+ if (cssString) {
94
+ wrapper.setAttribute('style', cssString);
95
+ }
96
+ }
97
+
98
+ wrapper.appendChild(icon);
99
+ node = wrapper;
100
+ }
101
+
102
+ if (targetElement) targetElement.appendChild(node);
103
+
104
+ this.logger.debug(`[IconRenderer] Rendered icon: %s`, `bi-${cleanName}`);
105
+ return node;
106
+ }
107
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ *
3
+ * Image Renderer - Media Display Events
4
+ *
5
+ *
6
+ * Terminal-First Design:
7
+ * - Backend sends src, alt_text, caption (works with URLs and local paths)
8
+ * - Terminal displays metadata + button to open
9
+ * - Bifrost renders <img> element with zTheme styling
10
+ *
11
+ * Renders image display events from zCLI backend. Creates image
12
+ * elements using primitives-first architecture.
13
+ *
14
+ * @module rendering/image_renderer
15
+ * @layer 3
16
+ * @pattern Primitives-First
17
+ *
18
+ * Dependencies:
19
+ * - None (uses native DOM API)
20
+ *
21
+ * Exports:
22
+ * - ImageRenderer: Class for rendering image events
23
+ *
24
+ * Example:
25
+ * ```javascript
26
+ * import ImageRenderer from './image_renderer.js';
27
+ *
28
+ * const renderer = new ImageRenderer(logger);
29
+ * const element = renderer.render({
30
+ * src: 'https://picsum.photos/200/300',
31
+ * alt_text: 'Random Image',
32
+ * caption: 'A beautiful picture',
33
+ * _zClass: 'zRounded-circle',
34
+ * _id: 'profile-pic'
35
+ * });
36
+ * ```
37
+ */
38
+
39
+ // ─────────────────────────────────────────────────────────────────
40
+ // Imports
41
+ // ─────────────────────────────────────────────────────────────────
42
+
43
+ // Layer 2: Utilities
44
+ import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
45
+
46
+ export class ImageRenderer {
47
+ constructor(logger) {
48
+ this.logger = logger;
49
+
50
+ // Wrap render method with error boundary
51
+ const originalRender = this.render.bind(this);
52
+ this.render = withErrorBoundary(originalRender, {
53
+ component: 'ImageRenderer',
54
+ logger: this.logger
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Render image element from primitives
60
+ *
61
+ * Terminal-First Design:
62
+ * - src: Supports ANY URL (external) or path (local/relative)
63
+ * - alt_text: Accessibility text (terminal shows this as header)
64
+ * - caption: Optional caption (terminal shows below path)
65
+ * - _zClass: Bifrost-only styling (e.g., zRounded-circle)
66
+ * - _id: Bifrost-only DOM id for targeting
67
+ *
68
+ * @param {Object} eventData - Event data from backend
69
+ * @param {string} eventData.src - Image source (URL or path)
70
+ * @param {string} [eventData.alt_text] - Alternative text for accessibility
71
+ * @param {string} [eventData.caption] - Optional caption text
72
+ * @param {string} [eventData._zClass] - Custom classes for styling
73
+ * @param {string} [eventData._id] - Custom id for targeting
74
+ * @returns {HTMLElement} Image container element
75
+ */
76
+ render(eventData) {
77
+ const { src, alt_text, caption, _zClass, _id } = eventData;
78
+
79
+ if (!src) {
80
+ this.logger.error('[ImageRenderer] Missing src parameter');
81
+ return this._createErrorElement();
82
+ }
83
+
84
+ this.logger.debug(`[ImageRenderer] Rendering image: ${src}`);
85
+
86
+ // Create img element (THE primitive) - align with zTheme
87
+ const img = document.createElement('img');
88
+ img.src = src; // Supports ANY URL (picsum, external, etc.) or local path
89
+
90
+ // Apply _zClass and _id directly to img (zTheme pattern)
91
+ if (_zClass) {
92
+ img.className = _zClass;
93
+ }
94
+ if (_id) {
95
+ img.setAttribute('id', _id);
96
+ }
97
+
98
+ // Add alt text for accessibility
99
+ if (alt_text) {
100
+ img.alt = alt_text;
101
+ } else {
102
+ img.alt = ''; // Empty alt for decorative images
103
+ }
104
+
105
+ // DON'T force inline styles - let zTheme/user control via classes
106
+ // If user wants object-fit, they can add "zObject-fit-cover" to _zClass
107
+
108
+ // Only wrap in <figure> if there's a caption (semantic HTML)
109
+ if (caption) {
110
+ const figure = document.createElement('figure');
111
+ figure.appendChild(img);
112
+
113
+ const figcaption = document.createElement('figcaption');
114
+ figcaption.textContent = caption;
115
+ figcaption.className = 'zText-muted zText-center zmt-2';
116
+ figure.appendChild(figcaption);
117
+
118
+ // _zClass/_zStyle already live on the inner <img> — tell the orchestrator's
119
+ // central metadata pass not to also stamp them onto this <figure> wrapper.
120
+ figure.__zMetaScoped = true;
121
+
122
+ this.logger.log('[ImageRenderer] Image with caption rendered');
123
+ return figure;
124
+ }
125
+
126
+ // No caption = return bare img (primitives-first + zTheme alignment!)
127
+ this.logger.debug('[ImageRenderer] Image rendered');
128
+ return img;
129
+ }
130
+
131
+ /**
132
+ * Create error element when src is missing
133
+ * @private
134
+ * @returns {HTMLElement}
135
+ */
136
+ _createErrorElement() {
137
+ const error = document.createElement('div');
138
+ error.className = 'zAlert zAlert-danger';
139
+ error.textContent = '[WARN] Image source missing';
140
+ return error;
141
+ }
142
+ }
143
+
144
+ export default ImageRenderer;
145
+
@@ -0,0 +1,190 @@
1
+ /**
2
+ * ListRenderer - Renders list elements (ul/ol) with zTheme styling
3
+ * Part of the modular bifrost rendering architecture
4
+ */
5
+
6
+ // ─────────────────────────────────────────────────────────────────
7
+ // Imports
8
+ // ─────────────────────────────────────────────────────────────────
9
+
10
+ // Layer 2: Utilities
11
+ import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
12
+
13
+ export class ListRenderer {
14
+ constructor(logger, client) {
15
+ this.client = client;
16
+ this.logger = logger;
17
+
18
+ // Wrap render method with error boundary
19
+ const originalRender = this.render.bind(this);
20
+ this.render = withErrorBoundary(originalRender, {
21
+ component: 'ListRenderer',
22
+ logger: this.logger
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Render a list element (bulleted or numbered)
28
+ * Supports plain text items, nested arrays, and nested zDisplay events
29
+ * NEW v1.7: Supports cascading styles for nested arrays!
30
+ * @param {Object} eventData - zDisplay event data with items array
31
+ * @param {number} level - Internal: current nesting level for cascading (default: 0)
32
+ * @returns {Promise<HTMLElement>} - Rendered list element (ul or ol)
33
+ */
34
+ async render(eventData, level = 0) {
35
+ this.logger.debug(`[ListRenderer] Rendering list: ${eventData.items?.length || 0} items`);
36
+
37
+ // Determine current style based on cascading
38
+ let currentStyle;
39
+ let cascadeStyles = null;
40
+
41
+ if (Array.isArray(eventData.style)) {
42
+ // Cascading styles: cycle through array based on nesting level
43
+ cascadeStyles = eventData.style;
44
+ currentStyle = eventData.style[level % eventData.style.length];
45
+ this.logger.log(`[ListRenderer] Using cascading style: ${currentStyle} (level ${level})`);
46
+ } else {
47
+ // Single style: use for all levels
48
+ currentStyle = eventData.style || 'bullet';
49
+ }
50
+
51
+ // Determine list element type and CSS list-style-type
52
+ let listElement;
53
+ let listStyleType = null;
54
+
55
+ if (currentStyle === 'number') {
56
+ listElement = document.createElement('ol');
57
+ } else if (currentStyle === 'letter') {
58
+ listElement = document.createElement('ol');
59
+ listStyleType = 'lower-alpha'; // a, b, c
60
+ } else if (currentStyle === 'roman') {
61
+ listElement = document.createElement('ol');
62
+ listStyleType = 'lower-roman'; // i, ii, iii
63
+ } else if (currentStyle === 'circle') {
64
+ listElement = document.createElement('ul');
65
+ listStyleType = 'circle'; //
66
+ } else if (currentStyle === 'square') {
67
+ listElement = document.createElement('ul');
68
+ listStyleType = 'square'; //
69
+ } else {
70
+ // bullet (default) or any other style
71
+ listElement = document.createElement('ul');
72
+ }
73
+
74
+ // Apply base zTheme class
75
+ listElement.className = 'zList';
76
+
77
+ // Apply list-style-type if specified
78
+ if (listStyleType) {
79
+ listElement.style.listStyleType = listStyleType;
80
+ }
81
+
82
+ // _zClass is applied centrally by the orchestrator (SSOT, append mode) on the
83
+ // returned listElement — we only read it below to detect inline layout.
84
+
85
+ // TODO: DEPRECATE - Remove `indent` property from list events
86
+ // Rationale: Conflicts with native HTML nesting, redundant with _zClass/_zStyle,
87
+ // only used in 2 places (markdown parser, traceback). Natural nesting is better.
88
+ // NOTE: Manual removal only - do not auto-fix with agents
89
+ // Apply indent using zms (margin-start) classes
90
+ if (eventData.indent && eventData.indent > 0) {
91
+ listElement.className += ` zms-${eventData.indent}`;
92
+ }
93
+
94
+ // Apply custom id if provided (_id parameter - ignored by terminal)
95
+ if (level === 0 && eventData._id) {
96
+ listElement.setAttribute('id', eventData._id);
97
+ }
98
+
99
+ // Check if this is an inline list (for horizontal layout)
100
+ const isInline = level === 0 && eventData._zClass && eventData._zClass.includes('zList-inline');
101
+
102
+ // Render list items (async to support nested zDisplay events)
103
+ const items = eventData.items || [];
104
+ let lastLi = null; // Track last created <li> for appending nested lists
105
+
106
+ for (const item of items) {
107
+ // NEW v1.7: Handle nested arrays naturally!
108
+ if (Array.isArray(item)) {
109
+ this.logger.debug('[ListRenderer] Rendering nested array');
110
+
111
+ // Nested array should be appended to the PREVIOUS list item
112
+ if (lastLi) {
113
+ try {
114
+ const nestedEventData = {
115
+ items: item,
116
+ style: cascadeStyles || currentStyle, // Pass cascading styles or current style
117
+ indent: 0 // Nested lists use native HTML indentation
118
+ };
119
+ const nestedList = await this.render(nestedEventData, level + 1);
120
+ lastLi.appendChild(nestedList);
121
+ } catch (error) {
122
+ this.logger.error('[ListRenderer] Error rendering nested array:', error);
123
+ lastLi.textContent += ` [Error: ${error.message}]`;
124
+ }
125
+ } else {
126
+ this.logger.warn('[ListRenderer] Nested array without previous list item - creating standalone');
127
+ // Fallback: create a new <li> if no previous item exists
128
+ const li = document.createElement('li');
129
+ try {
130
+ const nestedEventData = {
131
+ items: item,
132
+ style: cascadeStyles || currentStyle,
133
+ indent: 0
134
+ };
135
+ const nestedList = await this.render(nestedEventData, level + 1);
136
+ li.appendChild(nestedList);
137
+ listElement.appendChild(li);
138
+ lastLi = li;
139
+ } catch (error) {
140
+ this.logger.error('[ListRenderer] Error rendering nested array:', error);
141
+ li.textContent = `[Error: ${error.message}]`;
142
+ listElement.appendChild(li);
143
+ lastLi = li;
144
+ }
145
+ }
146
+ // Don't update lastLi - nested arrays attach to previous item
147
+ continue;
148
+ }
149
+
150
+ // Create new <li> for non-array items
151
+ const li = document.createElement('li');
152
+
153
+ // Apply zList-inline-item class if this is an inline list
154
+ if (isInline) {
155
+ li.className = 'zList-inline-item';
156
+ }
157
+
158
+ // Check if item contains a nested zDisplay event
159
+ if (item && typeof item === 'object' && item.zDisplay) {
160
+ this.logger.debug('[ListRenderer] Rendering nested zDisplay event');
161
+
162
+ // Recursively render the nested zDisplay event
163
+ try {
164
+ const nestedElement = await this.client.zDisplayOrchestrator.renderZDisplayEvent(item.zDisplay);
165
+ if (nestedElement) {
166
+ li.appendChild(nestedElement);
167
+ } else {
168
+ this.logger.warn('[ListRenderer] Nested zDisplay returned null, using fallback');
169
+ li.textContent = JSON.stringify(item);
170
+ }
171
+ } catch (error) {
172
+ this.logger.error('[ListRenderer] Error rendering nested zDisplay:', error);
173
+ li.textContent = `[Error: ${error.message}]`;
174
+ }
175
+ } else {
176
+ // Plain text item (original behavior)
177
+ const content = typeof item === 'string' ? item : (item.content || '');
178
+ li.textContent = content;
179
+ }
180
+
181
+ listElement.appendChild(li);
182
+ lastLi = li; // Track this as the last created <li>
183
+ }
184
+
185
+ this.logger.log(`[ListRenderer] Rendered ${currentStyle} list with ${items.length} items`);
186
+ return listElement;
187
+ }
188
+ }
189
+
190
+ export default ListRenderer;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Output Rendering Module Barrel Export
3
+ *
4
+ * Data output components (tables, text, cards, alerts, images, DL, etc.).
5
+ *
6
+ * @module rendering/outputs
7
+ * @layer 3 (Output Rendering)
8
+ */
9
+
10
+ export * from './alert_renderer.js';
11
+ export * from './card_renderer.js';
12
+ export * from './dl_renderer.js';
13
+ export * from './header_renderer.js';
14
+ export * from './icon_renderer.js';
15
+ export * from './image_renderer.js';
16
+ export * from './list_renderer.js';
17
+ export * from './table_renderer.js';
18
+ export * from './text_renderer.js';
19
+ export * from './typography_renderer.js';