@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,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ═══════════════════════════════════════════════════════════════
|
|
3
|
+
* Encoding Utilities - String Encoding/Decoding (Layer 2)
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* Pure utility functions for string encoding and decoding operations.
|
|
7
|
+
* Extracted from duplicate implementations in 4 renderer files.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/encoding_utils
|
|
10
|
+
* @layer 2 (Utilities - Pure functions)
|
|
11
|
+
*
|
|
12
|
+
* Dependencies: None (Layer 0 - Browser APIs only)
|
|
13
|
+
*
|
|
14
|
+
* Exports:
|
|
15
|
+
* - decodeUnicodeEscapes(text): Decode Unicode escape sequences
|
|
16
|
+
* - escapeHtml(text): Escape the 5 HTML-significant chars (SSOT — text & attr safe)
|
|
17
|
+
* - safeHref(url): Block dangerous URL schemes + attr-escape (SSOT for href values)
|
|
18
|
+
*
|
|
19
|
+
* Example:
|
|
20
|
+
* ```javascript
|
|
21
|
+
* import { decodeUnicodeEscapes, escapeHtml, safeHref } from '../../zSys/dom/encoding_utils.js';
|
|
22
|
+
* const decoded = decodeUnicodeEscapes('Hello \\u2764\\uFE0F'); // "Hello ❤️"
|
|
23
|
+
* el.innerHTML = `<a href="${safeHref(url)}">${escapeHtml(label)}</a>`;
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────
|
|
28
|
+
// HTML Escaping (SSOT) — replaces per-renderer ad-hoc escape chains
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const _HTML_ESCAPES = {
|
|
32
|
+
'&': '&',
|
|
33
|
+
'<': '<',
|
|
34
|
+
'>': '>',
|
|
35
|
+
'"': '"',
|
|
36
|
+
"'": ''',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Escape the five HTML-significant characters. Safe for both element text and
|
|
41
|
+
* double/single-quoted attribute contexts (escapes both " and '). Deterministic,
|
|
42
|
+
* no DOM dependency. This is the single source of truth for HTML escaping in the
|
|
43
|
+
* client — renderers must not hand-roll their own `.replace(/&/g, ...)` chains.
|
|
44
|
+
*
|
|
45
|
+
* @param {*} text - Value to escape (coerced to string; null/undefined → '')
|
|
46
|
+
* @returns {string} Escaped string
|
|
47
|
+
*/
|
|
48
|
+
export function escapeHtml(text) {
|
|
49
|
+
if (text === null || text === undefined) return '';
|
|
50
|
+
return String(text).replace(/[&<>"']/g, (ch) => _HTML_ESCAPES[ch]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Schemes that can execute script / smuggle payloads when placed in href/src.
|
|
54
|
+
const _DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sanitize a URL for safe embedding in an href/src attribute. Blocks dangerous
|
|
58
|
+
* schemes (javascript:, data:, vbscript:) — including whitespace/control-char
|
|
59
|
+
* obfuscation — then attribute-escapes the result. Returns '#' for blocked or
|
|
60
|
+
* empty input. http(s)/mailto/tel/anchor/relative/zPath URLs pass through.
|
|
61
|
+
*
|
|
62
|
+
* @param {*} url - Candidate URL (already zPath-resolved by the caller)
|
|
63
|
+
* @returns {string} Safe, attribute-escaped URL or '#'
|
|
64
|
+
*/
|
|
65
|
+
export function safeHref(url) {
|
|
66
|
+
if (url === null || url === undefined) return '#';
|
|
67
|
+
const raw = String(url).trim();
|
|
68
|
+
if (!raw) return '#';
|
|
69
|
+
// Strip whitespace + control chars before the scheme test so "java\tscript:"
|
|
70
|
+
// and " javascript:" cannot slip past the guard.
|
|
71
|
+
const probe = raw.replace(/[\u0000-\u001F\u007F\s]/g, '');
|
|
72
|
+
if (_DANGEROUS_SCHEME.test(probe)) return '#';
|
|
73
|
+
return escapeHtml(raw);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────
|
|
77
|
+
// Unicode Decoding
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Decode Unicode escape sequences in text
|
|
82
|
+
*
|
|
83
|
+
* Handles multiple Unicode escape formats:
|
|
84
|
+
* - \uXXXX: Standard 4-digit Unicode escape (e.g., \u2764 → ❤)
|
|
85
|
+
* - \UXXXXXXXX: Extended 4-8 digit for supplementary characters & emojis
|
|
86
|
+
* - Basic escape sequences: \n, \t, \r, etc.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} text - Text containing Unicode escapes
|
|
89
|
+
* @returns {string} Decoded text
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* decodeUnicodeEscapes('Hello \\u2764\\uFE0F'); // "Hello ❤️"
|
|
93
|
+
* decodeUnicodeEscapes('Line 1\\nLine 2'); // "Line 1\nLine 2"
|
|
94
|
+
* decodeUnicodeEscapes('\\U0001F600'); // "😀"
|
|
95
|
+
*/
|
|
96
|
+
export function decodeUnicodeEscapes(text) {
|
|
97
|
+
if (!text || typeof text !== 'string') return text;
|
|
98
|
+
|
|
99
|
+
// Replace \uXXXX format (standard 4-digit Unicode escape)
|
|
100
|
+
text = text.replace(/\\u([0-9A-Fa-f]{4})/g, (match, hexCode) => {
|
|
101
|
+
return String.fromCodePoint(parseInt(hexCode, 16));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Replace \UXXXXXXXX format (extended 4-8 digit for supplementary characters & emojis)
|
|
105
|
+
text = text.replace(/\\U([0-9A-Fa-f]{4,8})/g, (match, hexCode) => {
|
|
106
|
+
return String.fromCodePoint(parseInt(hexCode, 16));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Replace basic escape sequences (literal strings like \\n, \\t, etc.)
|
|
110
|
+
// These come from JSON where Python sends "\n" which becomes "\\n" in JSON
|
|
111
|
+
text = text.replace(/\\n/g, '\n');
|
|
112
|
+
text = text.replace(/\\r/g, '\r');
|
|
113
|
+
text = text.replace(/\\t/g, '\t');
|
|
114
|
+
text = text.replace(/\\\\/g, '\\');
|
|
115
|
+
|
|
116
|
+
return text;
|
|
117
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zSys/dom/style_utils.js
|
|
3
|
+
*
|
|
4
|
+
* Style Conversion Utilities
|
|
5
|
+
*
|
|
6
|
+
* Handles _zStyle metadata conversion from various formats to CSS strings.
|
|
7
|
+
* Supports both legacy inline string format and new nested object syntax.
|
|
8
|
+
*
|
|
9
|
+
* Created: 2026-04-19 - Phase 1.0 - _zStyle nested syntax migration
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert _zStyle to CSS string
|
|
14
|
+
*
|
|
15
|
+
* Supports two formats:
|
|
16
|
+
* 1. String (legacy): "color: red; font-size: 16px"
|
|
17
|
+
* 2. Object (nested): { "color": "red", "font-size": "16px" }
|
|
18
|
+
*
|
|
19
|
+
* @param {string|Object} style - CSS string or object with CSS properties
|
|
20
|
+
* @param {Object} [logger=null] - Optional logger for warnings
|
|
21
|
+
* @returns {string} CSS string for style attribute (empty string if invalid)
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // String passthrough (legacy/inline)
|
|
25
|
+
* convertStyleToString("color: red; font-size: 16px")
|
|
26
|
+
* // => "color: red; font-size: 16px"
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Object to CSS string (new nested syntax)
|
|
30
|
+
* convertStyleToString({
|
|
31
|
+
* "border-bottom": "2px solid var(--color-primary)",
|
|
32
|
+
* "background": "red",
|
|
33
|
+
* "letter-spacing": "0.1em"
|
|
34
|
+
* })
|
|
35
|
+
* // => "border-bottom: 2px solid var(--color-primary); background: red; letter-spacing: 0.1em"
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // CSS variables work in both formats
|
|
39
|
+
* convertStyleToString({ "color": "var(--color-primary)" })
|
|
40
|
+
* // => "color: var(--color-primary)"
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Empty/null/undefined values filtered out
|
|
44
|
+
* convertStyleToString({ "color": "red", "background": null, "font-size": "" })
|
|
45
|
+
* // => "color: red"
|
|
46
|
+
*/
|
|
47
|
+
export function convertStyleToString(style, logger = null) {
|
|
48
|
+
// Handle null/undefined
|
|
49
|
+
if (style === null || style === undefined || style === '') {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Legacy format: inline CSS string
|
|
54
|
+
if (typeof style === 'string') {
|
|
55
|
+
return style.trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// New format: nested object (CSS-in-YAML)
|
|
59
|
+
if (typeof style === 'object' && !Array.isArray(style)) {
|
|
60
|
+
const cssString = Object.entries(style)
|
|
61
|
+
.filter(([_, value]) => value !== undefined && value !== null && value !== '')
|
|
62
|
+
.map(([prop, value]) => `${prop}: ${value}`)
|
|
63
|
+
.join('; ');
|
|
64
|
+
|
|
65
|
+
return cssString;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Invalid type
|
|
69
|
+
logger?.warn(`[StyleUtils] Invalid _zStyle type: ${typeof style}`, style);
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Error Display Module - Visual Error Boundaries for UI
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class ErrorDisplay {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.containerId = options.containerId || 'bifrost-errors';
|
|
10
|
+
this.autoCreate = options.autoCreate !== false; // Default true
|
|
11
|
+
this.maxErrors = options.maxErrors || 5; // Max errors to show
|
|
12
|
+
this.autoDismiss = options.autoDismiss || 10000; // TIMEOUTS.AUTO_DISMISS (UMD limitation, can't import)
|
|
13
|
+
this.position = options.position || 'top-right'; // top-right, top-left, bottom-right, bottom-left
|
|
14
|
+
|
|
15
|
+
this.errors = [];
|
|
16
|
+
this.container = null;
|
|
17
|
+
// Dedup: collapse repeated identical errors (e.g. WebSocket reconnect loops,
|
|
18
|
+
// missing _requestId) into ONE toast with a ×N counter. Repeats after the
|
|
19
|
+
// first go to the console only — dev-accurate without the UX spam.
|
|
20
|
+
this._active = new Map(); // key -> { el, countEl, count, timer }
|
|
21
|
+
|
|
22
|
+
if (this.autoCreate) {
|
|
23
|
+
this._createContainer();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create error container if it doesn't exist
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
_createContainer() {
|
|
32
|
+
// Check if container already exists
|
|
33
|
+
this.container = document.getElementById(this.containerId);
|
|
34
|
+
|
|
35
|
+
if (!this.container) {
|
|
36
|
+
this.container = document.createElement('div');
|
|
37
|
+
this.container.id = this.containerId;
|
|
38
|
+
|
|
39
|
+
// Canonical, class-based styling (SSOT in zbase.css) — no inline styles.
|
|
40
|
+
// A corner modifier selects the fixed position.
|
|
41
|
+
const corner = ['top-right', 'top-left', 'bottom-right', 'bottom-left']
|
|
42
|
+
.includes(this.position) ? this.position : 'top-right';
|
|
43
|
+
this.container.className =
|
|
44
|
+
`bifrost-error-container bifrost-error-container--${corner}`;
|
|
45
|
+
|
|
46
|
+
// Centralize under the <zVaF> root (SSOT — all bifrost chrome lives there);
|
|
47
|
+
// fall back to <body> if the root isn't present yet.
|
|
48
|
+
const root = document.querySelector('zVaF') || document.body;
|
|
49
|
+
root.appendChild(this.container);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Display an error in the UI
|
|
55
|
+
*/
|
|
56
|
+
show(errorInfo) {
|
|
57
|
+
if (!this.container) {
|
|
58
|
+
this._createContainer();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Dedup ──────────────────────────────────────────────────────────────
|
|
62
|
+
// Same title+message already on screen → don't stack another toast. Bump its
|
|
63
|
+
// ×N counter, refresh its auto-dismiss, and log the repeat to the console.
|
|
64
|
+
const dedupeKey = `${this._getErrorTitle(errorInfo)}::${errorInfo.message || ''}`;
|
|
65
|
+
const existing = this._active.get(dedupeKey);
|
|
66
|
+
if (existing && existing.el && existing.el.parentNode) {
|
|
67
|
+
existing.count += 1;
|
|
68
|
+
if (existing.countEl) existing.countEl.textContent = ` ×${existing.count}`;
|
|
69
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
70
|
+
console.warn('[Bifrost][repeat suppressed]', dedupeKey, errorInfo);
|
|
71
|
+
}
|
|
72
|
+
if (existing.timer) clearTimeout(existing.timer);
|
|
73
|
+
if (this.autoDismiss) {
|
|
74
|
+
existing.timer = setTimeout(() => this._dismissError(existing.el), this.autoDismiss);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create error element
|
|
80
|
+
const errorEl = document.createElement('div');
|
|
81
|
+
errorEl.className = 'bifrost-error';
|
|
82
|
+
errorEl.style.cssText = `
|
|
83
|
+
background: #fee;
|
|
84
|
+
border-left: 4px solid #c33;
|
|
85
|
+
padding: 1rem;
|
|
86
|
+
margin-bottom: 0.5rem;
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
89
|
+
pointer-events: auto;
|
|
90
|
+
animation: slideIn 0.3s ease-out;
|
|
91
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
92
|
+
font-size: 0.875rem;
|
|
93
|
+
line-height: 1.5;
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
// Error title
|
|
97
|
+
const title = document.createElement('div');
|
|
98
|
+
title.style.cssText = `
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
color: #c33;
|
|
101
|
+
margin-bottom: 0.5rem;
|
|
102
|
+
display: flex;
|
|
103
|
+
justify-content: space-between;
|
|
104
|
+
align-items: center;
|
|
105
|
+
`;
|
|
106
|
+
title.innerHTML = `
|
|
107
|
+
<span>[WARN] ${this._getErrorTitle(errorInfo)}<span class="bifrost-error-count" style="font-weight:600;"></span></span>
|
|
108
|
+
<button style="
|
|
109
|
+
background: none;
|
|
110
|
+
border: none;
|
|
111
|
+
color: #c33;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
font-size: 1.2rem;
|
|
114
|
+
padding: 0;
|
|
115
|
+
margin-left: 0.5rem;
|
|
116
|
+
line-height: 1;
|
|
117
|
+
" title="Dismiss">×</button>
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
// Error message
|
|
121
|
+
const message = document.createElement('div');
|
|
122
|
+
message.style.cssText = 'color: #600; margin-bottom: 0.5rem;';
|
|
123
|
+
message.textContent = errorInfo.message || 'An error occurred';
|
|
124
|
+
|
|
125
|
+
// Error details (collapsible)
|
|
126
|
+
const details = document.createElement('details');
|
|
127
|
+
details.style.cssText = 'color: #800; font-size: 0.75rem; margin-top: 0.5rem;';
|
|
128
|
+
details.innerHTML = `
|
|
129
|
+
<summary style="cursor: pointer; user-select: none;">Show details</summary>
|
|
130
|
+
<pre style="
|
|
131
|
+
margin-top: 0.5rem;
|
|
132
|
+
padding: 0.5rem;
|
|
133
|
+
background: #fff;
|
|
134
|
+
border-radius: 3px;
|
|
135
|
+
overflow-x: auto;
|
|
136
|
+
font-size: 0.7rem;
|
|
137
|
+
white-space: pre-wrap;
|
|
138
|
+
word-wrap: break-word;
|
|
139
|
+
">${this._formatStack(errorInfo)}</pre>
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
errorEl.appendChild(title);
|
|
143
|
+
errorEl.appendChild(message);
|
|
144
|
+
errorEl.appendChild(details);
|
|
145
|
+
|
|
146
|
+
// Add dismiss handler
|
|
147
|
+
const dismissBtn = title.querySelector('button');
|
|
148
|
+
dismissBtn.onclick = () => this._dismissError(errorEl);
|
|
149
|
+
|
|
150
|
+
// Add to container
|
|
151
|
+
this.container.appendChild(errorEl);
|
|
152
|
+
this.errors.push(errorEl);
|
|
153
|
+
|
|
154
|
+
// Register as the active toast for this dedupe key
|
|
155
|
+
const countEl = title.querySelector('.bifrost-error-count');
|
|
156
|
+
const entry = { el: errorEl, countEl, count: 1, timer: null, key: dedupeKey };
|
|
157
|
+
errorEl.dataset.dedupeKey = dedupeKey;
|
|
158
|
+
this._active.set(dedupeKey, entry);
|
|
159
|
+
|
|
160
|
+
// Remove oldest error if we exceed max
|
|
161
|
+
if (this.errors.length > this.maxErrors) {
|
|
162
|
+
const oldest = this.errors.shift();
|
|
163
|
+
if (oldest && oldest.parentNode) {
|
|
164
|
+
oldest.remove();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Auto-dismiss after timeout
|
|
169
|
+
if (this.autoDismiss) {
|
|
170
|
+
entry.timer = setTimeout(() => this._dismissError(errorEl), this.autoDismiss);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add slide-in animation
|
|
174
|
+
this._addAnimationStyles();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Dismiss an error
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
_dismissError(errorEl) {
|
|
182
|
+
if (!errorEl || !errorEl.parentNode) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Fade out animation
|
|
187
|
+
errorEl.style.animation = 'fadeOut 0.3s ease-out';
|
|
188
|
+
|
|
189
|
+
// Release the dedupe slot so a later occurrence can toast fresh
|
|
190
|
+
const key = errorEl.dataset ? errorEl.dataset.dedupeKey : null;
|
|
191
|
+
if (key && this._active.get(key)?.el === errorEl) {
|
|
192
|
+
const entry = this._active.get(key);
|
|
193
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
194
|
+
this._active.delete(key);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
if (errorEl.parentNode) {
|
|
199
|
+
errorEl.remove();
|
|
200
|
+
}
|
|
201
|
+
const index = this.errors.indexOf(errorEl);
|
|
202
|
+
if (index > -1) {
|
|
203
|
+
this.errors.splice(index, 1);
|
|
204
|
+
}
|
|
205
|
+
}, 300); // TIMEOUTS.FADE_TRANSITION (UMD limitation, can't import)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get error title based on error type
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
_getErrorTitle(errorInfo) {
|
|
213
|
+
if (errorInfo.type === 'hook_error') {
|
|
214
|
+
return `Hook Error: ${errorInfo.hookName}`;
|
|
215
|
+
}
|
|
216
|
+
if (errorInfo.type === 'render_error') {
|
|
217
|
+
return 'Rendering Error';
|
|
218
|
+
}
|
|
219
|
+
if (errorInfo.type === 'connection_error') {
|
|
220
|
+
return 'Connection Error';
|
|
221
|
+
}
|
|
222
|
+
return 'BifrostClient Error';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Format error stack for display
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
_formatStack(errorInfo) {
|
|
230
|
+
if (errorInfo.stack) {
|
|
231
|
+
return errorInfo.stack;
|
|
232
|
+
}
|
|
233
|
+
if (errorInfo.error && errorInfo.error.stack) {
|
|
234
|
+
return errorInfo.error.stack;
|
|
235
|
+
}
|
|
236
|
+
return JSON.stringify(errorInfo, null, 2);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Add animation styles to document
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
_addAnimationStyles() {
|
|
244
|
+
if (document.getElementById('bifrost-error-styles')) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const style = document.createElement('style');
|
|
249
|
+
style.id = 'bifrost-error-styles';
|
|
250
|
+
style.textContent = `
|
|
251
|
+
@keyframes slideIn {
|
|
252
|
+
from {
|
|
253
|
+
transform: translateX(100%);
|
|
254
|
+
opacity: 0;
|
|
255
|
+
}
|
|
256
|
+
to {
|
|
257
|
+
transform: translateX(0);
|
|
258
|
+
opacity: 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@keyframes fadeOut {
|
|
263
|
+
from {
|
|
264
|
+
opacity: 1;
|
|
265
|
+
}
|
|
266
|
+
to {
|
|
267
|
+
opacity: 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
`;
|
|
271
|
+
document.head.appendChild(style);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Clear all errors
|
|
276
|
+
*/
|
|
277
|
+
clearAll() {
|
|
278
|
+
this.errors.forEach(errorEl => {
|
|
279
|
+
if (errorEl.parentNode) {
|
|
280
|
+
errorEl.remove();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
this.errors = [];
|
|
284
|
+
this._active.forEach(entry => { if (entry.timer) clearTimeout(entry.timer); });
|
|
285
|
+
this._active.clear();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Destroy error display (remove container)
|
|
290
|
+
*/
|
|
291
|
+
destroy() {
|
|
292
|
+
this.clearAll();
|
|
293
|
+
if (this.container && this.container.parentNode) {
|
|
294
|
+
this.container.remove();
|
|
295
|
+
}
|
|
296
|
+
this.container = null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|