@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,111 @@
1
+ # Architecture
2
+
3
+ The client is split into a tiny **bootstrap** and a lazily-loaded **core**, layered
4
+ `L1 → L4` with a cross-cutting `zSys` utility layer. Nothing here knows about your
5
+ application — it only knows how to open a socket, decode events, and build DOM.
6
+
7
+ ## Bootstrap / core split
8
+
9
+ ```
10
+ page <head>
11
+ └─ bifrost_client.js (BifrostClient, ~190 LOC, hardcoded & pinned in HTML)
12
+ 1. read #zui-config (server-injected JSON)
13
+ 2. open WebSocket (ws[s]://host:port from config)
14
+ 3. recv connection_info { bifrost_core_url, nav_html, session, ... }
15
+ 4. import(bifrost_core_url) ← server picks the core version
16
+ 5. new BifrostCore(url, opts) → window.bifrostClient
17
+ 6. replay any hooks queued before the core was ready
18
+ ```
19
+
20
+ Why two files:
21
+
22
+ - **`bifrost_client.js`** is small, stable, and pinned in the page. It almost never
23
+ changes, so it can be cached hard and rarely needs a redeploy of the host page.
24
+ - **`bifrost_core.js`** holds all the logic and is selected **by the server** at
25
+ connect time (`connection_info.bifrost_core_url`). The server therefore controls
26
+ exactly which renderer build every client runs — no page edit required to ship a
27
+ new client.
28
+
29
+ The bootstrap origin-pins that import; see
30
+ [SECURITY.md](SECURITY.md#core-import-origin-pinning).
31
+
32
+ > `bifrost_core.js` is intentionally written **without top-level `import`** so it can
33
+ > also be consumed as a plain `<script>` (UMD-style). Its lazily-loaded submodules
34
+ > (everything under `L1`–`L4`/`zSys`) are standard ES modules pulled in via dynamic
35
+ > `import()`.
36
+
37
+ ## Layers
38
+
39
+ | Layer | Path | Responsibility |
40
+ |-------|------|----------------|
41
+ | **L1 — Foundation** | `L1_Foundation/` | WebSocket connection, constants (incl. the protocol vocabulary), config parsing, bootstrap, module registry, CDN/Prism loaders |
42
+ | **L2 — Handling** | `L2_Handling/` | Message handling & correlation, the renderer set, cache, navigation, zVaF mounting, hooks |
43
+ | **L3 — Abstraction** | `L3_Abstraction/` | Orchestrators that compose handlers (navbar, wizard gate, session) |
44
+ | **L4 — Orchestration** | `L4_Orchestration/` | The public client facade, renderer/manager **registries**, lifecycle |
45
+ | **zSys** | `zSys/` | Cross-cutting utilities: DOM, theme, accessibility, validation, encoding (HTML escape SSOT) |
46
+
47
+ Imports flow **downward** (`L4 → L1 → zSys`); lower layers never import higher ones.
48
+
49
+ ## Registries & lazy loading
50
+
51
+ The core does not eagerly load 40+ renderer modules. Two registries map a logical
52
+ name to a module path + class and load on first use:
53
+
54
+ - **`L4_Orchestration/facade/renderer_registry.js`** — `RENDERER_REGISTRY` maps a
55
+ renderer key (`text`, `header`, `card`, `table`, `zMenu`, `zTerminal`, …) to its
56
+ module path and class. `ensureRenderer(key)` dynamic-imports and caches it.
57
+ - **`L4_Orchestration/facade/manager_registry.js`** — same pattern for managers
58
+ (cache, zVaF, navigation, hooks).
59
+
60
+ This keeps time-to-first-paint cheap: only the renderers a page actually uses are
61
+ fetched. (A `critical`/`deferred` preload tiering is sketched as a TODO in the
62
+ registry for further first-paint tuning.)
63
+
64
+ ## Module map (high level)
65
+
66
+ ```
67
+ L1_Foundation/
68
+ bootstrap/ bootstrap.js, cdn_loader.js, prism_loader.js, module_registry.js
69
+ config/ client_config.js, config.js (parse #zui-config)
70
+ connection/ websocket_connection.js (open/reconnect lifecycle)
71
+ constants/ bifrost_constants.js (TIMEOUTS, EVENT_TYPES,
72
+ PROTOCOL_EVENTS, CSS_CLASSES…)
73
+ logger/
74
+
75
+ L2_Handling/
76
+ message/ message_handler.js (parse → decode opcodes → dispatch)
77
+ display/
78
+ outputs/ text, typography, header, card, code, list, table, alert, icon,
79
+ image, dl …
80
+ inputs/ button, form, input, input_request
81
+ feedback/ progressbar, spinner
82
+ composite/ dashboard, swiper, terminal, wizard_conditional
83
+ navigation/ menu, navigation
84
+ primitives/ low-level DOM builders shared by renderers (links, typography,
85
+ tables, media, lists, semantic elements)
86
+ orchestration/ zdisplay_orchestrator.js
87
+ cache/ navigation/ zvaf/ hooks/
88
+
89
+ L3_Abstraction/ orchestrator/ renderer/ session/
90
+
91
+ L4_Orchestration/
92
+ facade/ facade.js, renderer_registry.js, manager_registry.js
93
+ rendering/ rendering facade
94
+ client/ lifecycle/
95
+
96
+ zSys/
97
+ dom/ dom_utils.js, style_utils.js, encoding_utils.js (escapeHtml/safeHref SSOT)
98
+ theme/ ztheme_utils.js
99
+ accessibility/ emoji_accessibility.js
100
+ errors/ validation/
101
+ ```
102
+
103
+ ## Lifecycle (one connection)
104
+
105
+ 1. Page instantiates `BifrostClient` → bootstrap connects, gets `connection_info`.
106
+ 2. `BifrostCore` connects its own socket and sends the first `execute_walker`.
107
+ 3. Server streams `render_chunk` messages (opcode-encoded display trees).
108
+ 4. `message_handler` decodes opcodes → display events and dispatches to renderers
109
+ via hooks (`onRenderChunk`, `onDisplay`, `onMenu`, …).
110
+ 5. Renderers build DOM into the `zVaF` mount; navigation/menus drive subsequent
111
+ walker calls. See [PROTOCOL.md](PROTOCOL.md).
@@ -0,0 +1,106 @@
1
+ # Wire Protocol (client side)
2
+
3
+ This describes the protocol **as the browser sees it**. The authoritative,
4
+ server-side encoding (how display trees become opcodes, auth, chunking) lives in
5
+ the zOS/zGuard repos and is deliberately not duplicated here. The client only
6
+ needs to: connect, decode, and dispatch.
7
+
8
+ All messages are JSON. The discriminator is the `event` field (a few legacy paths
9
+ also look at `display_event` / `type`).
10
+
11
+ ## 1. Handshake — `connection_info`
12
+
13
+ After the socket opens, the server sends exactly one `connection_info`:
14
+
15
+ ```jsonc
16
+ {
17
+ "event": "connection_info",
18
+ "data": {
19
+ "server_version": "…",
20
+ "bifrost_core_url": "https://cdn.jsdelivr.net/gh/ZoloAi/zbifrost-client@v1.7.52/bifrost_core.js",
21
+ "nav_html": "<nav>…</nav>", // pre-built, RBAC-filtered navbar
22
+ "session": { "authenticated": false, "username": null, "role": null, … },
23
+ "features": [ … ],
24
+ "available_models": [ … ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ - The **bootstrap** uses `bifrost_core_url` to `import()` the core (origin-pinned —
30
+ see [SECURITY.md](SECURITY.md#core-import-origin-pinning)).
31
+ - `nav_html` is consumed as-is; the client does not build navigation itself.
32
+ - `session` is display state only (navbar, RBAC-driven visibility). It is **not**
33
+ proof of identity — the server re-validates every request.
34
+
35
+ ## 2. Render stream — `render_chunk` + opcodes
36
+
37
+ The server streams the page as a sequence of `render_chunk` messages. To keep the
38
+ zOS display vocabulary off the wire, each render node's event name is encoded to a
39
+ short **opcode** on `node.e`. The client decodes it back before dispatching.
40
+
41
+ ```jsonc
42
+ // on the wire (encoded) // after _decodeRenderNode()
43
+ { "e": "tx", "content": "Hi" } → { "event": "text", "content": "Hi" }
44
+ { "e": "hd", "label": "Title" } → { "event": "header", "label": "Title" }
45
+ ```
46
+
47
+ The decode table `_ZRENDER_OPS` in `L2_Handling/message/message_handler.js` is a
48
+ **hand-maintained mirror** of the server SSOT
49
+ (`render_opcodes.py` → `EVENT_TO_OP`, 35 entries). The decoder:
50
+
51
+ - recurses into arrays and child nodes,
52
+ - decodes any node carrying a known opcode (`node.e`),
53
+ - **warns once** (`_warnUnknownOpcode`) on an unknown opcode instead of silently
54
+ dropping it — this surfaces client/server drift loudly. If you see
55
+ `[zRender] Unknown render opcode "…"`, the client mirror is behind the server.
56
+
57
+ > Opcodes are an obfuscation/compactness layer, **not** a security boundary. They
58
+ > carry no routes, field names, or wizard flow — only the display-event vocabulary.
59
+
60
+ ## 3. Control & display events — `PROTOCOL_EVENTS`
61
+
62
+ Top-level `message.event` values are the protocol vocabulary. They are the SSOT in
63
+ `L1_Foundation/constants/bifrost_constants.js` as **`PROTOCOL_EVENTS`** /
64
+ **`PROTOCOL_REASONS`** — `message_handler` dispatch references these constants, not
65
+ raw string literals.
66
+
67
+ | Group | Events |
68
+ |-------|--------|
69
+ | Transport / connection | `render_chunk`, `connection_info`, `navigate_back`, `error` |
70
+ | Display / output | `display`, `output`, `zTable`, `zDash`, `zMenu`, `zDialog`, `swiper_init` |
71
+ | Progress / spinner | `progress_bar`, `progress_update`, `progress_complete`, `spinner_start`, `spinner_stop` |
72
+ | Input req/res | `request_input`, `input_request`, `input_response` |
73
+ | Execution / wizard / RBAC | `execute_walker`, `execute_zfunc_response`, `execute_code_response`, `zfunc_exec`, `wizard_gate_result`, `rbac_denied` |
74
+ | Logging | `app_log` |
75
+
76
+ `navigate_back` carries a `reason` (`PROTOCOL_REASONS`):
77
+ `bounce_back_block_completed` (post-login/logout bounce) and `rbac_denied`
78
+ (access-denied redirect).
79
+
80
+ > `EVENT_TYPES` in the same file is a **separate** map — browser-native DOM event
81
+ > names (`click`, `change`, …) used with `addEventListener`. Do not confuse it with
82
+ > the wire `PROTOCOL_EVENTS`.
83
+
84
+ ## 4. Dispatch flow
85
+
86
+ ```
87
+ WebSocket message
88
+ → JSON.parse
89
+ → message.event === render_chunk ? decode opcodes → hooks.call('onRenderChunk')
90
+ → connection_info ? hooks.call('onConnectionInfo' / 'onConnected')
91
+ → error ? show alert + hooks.call('onError')
92
+ → navigate_back ? history.back() / client-side route by reason
93
+ → zDash / zMenu / rbac_denied ? dedicated hooks
94
+ → _requestId correlated ? resolve the pending request promise
95
+ → display / progress / spinner / input / swiper / app_log / zfunc_exec → hooks
96
+ → else broadcast
97
+ ```
98
+
99
+ Renderers subscribe to these hooks and build DOM. See [RENDERERS.md](RENDERERS.md).
100
+
101
+ ## 5. Outgoing
102
+
103
+ The client sends `execute_walker` (and friends) back to the server. When a session
104
+ cookie is readable it is attached to walker requests as a best-effort sync hint
105
+ (see [SECURITY.md](SECURITY.md#session-cookie-handling)); the server remains the
106
+ authority.
@@ -0,0 +1,101 @@
1
+ # Renderers
2
+
3
+ A renderer turns one decoded display event into DOM. Renderers are **pure**: no
4
+ WebSocket, no app state, no side effects beyond producing/returning elements. They
5
+ consume Layer-2 *primitives* and `zSys` utilities rather than hand-rolling DOM or
6
+ escaping.
7
+
8
+ ## The model
9
+
10
+ - Each renderer is an ES-module class under `L2_Handling/display/<group>/`.
11
+ - It exposes render method(s) that accept a decoded event object (`eventData`) and
12
+ return an `HTMLElement` (or append into a container).
13
+ - It is registered in `L4_Orchestration/facade/renderer_registry.js` so it is
14
+ lazy-loaded on first use.
15
+
16
+ ```js
17
+ // L2_Handling/display/outputs/typography_renderer.js
18
+ export class TypographyRenderer {
19
+ constructor(logger) { this.logger = logger; }
20
+ renderText(eventData) {
21
+ // build & return an HTMLElement from eventData
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## The registry
27
+
28
+ `RENDERER_REGISTRY` maps a logical renderer key to its module path + class:
29
+
30
+ ```js
31
+ export const RENDERER_REGISTRY = {
32
+ text: { path: 'L2_Handling/display/outputs/text_renderer.js', className: 'TextRenderer', isDefault: true, passClient: false },
33
+ header: { path: 'L2_Handling/display/outputs/header_renderer.js', className: 'HeaderRenderer', isDefault: true, passClient: false },
34
+ table: { path: 'L2_Handling/display/outputs/table_renderer.js', className: 'TableRenderer', isDefault: true, passClient: false },
35
+ zMenu: { path: 'L2_Handling/display/navigation/menu_renderer.js', className: 'MenuRenderer', isDefault: true, passClient: true },
36
+ // …
37
+ };
38
+ ```
39
+
40
+ - `className` — the exported class name to instantiate.
41
+ - `passClient` — `true` if the renderer needs a reference to the live client (e.g.
42
+ navigation/menu renderers that trigger walker calls); `false` for pure output
43
+ renderers.
44
+ - `ensureRenderer(key)` dynamic-imports and caches the instance on first use.
45
+
46
+ The renderer **key** corresponds to the decoded `event` name from the wire
47
+ (`text`, `header`, `zTable`, `zMenu`, …) — see [PROTOCOL.md](PROTOCOL.md).
48
+
49
+ ## Renderer set (by group)
50
+
51
+ | Group | Renderers |
52
+ |-------|-----------|
53
+ | `outputs/` | text, typography, header, code, card, list, dl, table, alert, icon, image |
54
+ | `inputs/` | button, form, input, input_request |
55
+ | `feedback/` | progressbar, spinner |
56
+ | `composite/` | dashboard, swiper, terminal, wizard_conditional |
57
+ | `navigation/` | menu, navigation |
58
+ | `specialized/` | input_request |
59
+
60
+ Shared low-level DOM builders live in `L2_Handling/display/primitives/`
61
+ (`link_primitives`, `typography_primitives`, `table_primitives`, `media_primitives`,
62
+ `lists_primitives`, `semantic_element_primitive`, …). Prefer composing these over
63
+ writing raw element code in a renderer.
64
+
65
+ ## HTML escaping is centralized (SSOT)
66
+
67
+ Renderers must **never** hand-roll escape chains (`.replace(/&/g, …)`) or build
68
+ attribute strings by hand. Use the single source of truth in
69
+ `zSys/dom/encoding_utils.js`:
70
+
71
+ ```js
72
+ import { escapeHtml, safeHref } from '../../../zSys/dom/encoding_utils.js';
73
+
74
+ // element text or attribute value (escapes & < > " ')
75
+ el.innerHTML = `<span title="${escapeHtml(label)}">${escapeHtml(text)}</span>`;
76
+
77
+ // href/src values — blocks javascript:/data:/vbscript: then attr-escapes
78
+ a.innerHTML = `<a href="${safeHref(url)}">${escapeHtml(label)}</a>`;
79
+ ```
80
+
81
+ - `escapeHtml(value)` — escapes the five HTML-significant characters; safe for both
82
+ text and quoted-attribute contexts; deterministic, no DOM dependency.
83
+ - `safeHref(url)` — sanitizes a URL for an `href`/`src` attribute: blocks dangerous
84
+ schemes (including whitespace/control-char obfuscation), returns `#` for blocked
85
+ or empty input, otherwise attribute-escapes. Run resolved/zPath URLs through it.
86
+
87
+ Rationale and the broader trust boundary are in [SECURITY.md](SECURITY.md).
88
+
89
+ ## Adding a renderer
90
+
91
+ 1. Create `L2_Handling/display/<group>/<name>_renderer.js` exporting a class with a
92
+ `render…(eventData)` method that returns an `HTMLElement`.
93
+ 2. Build DOM via `display/primitives/*` and `zSys/dom/*`; escape every dynamic value
94
+ through `escapeHtml` / `safeHref`.
95
+ 3. Add an entry to `RENDERER_REGISTRY` keyed by the decoded event name; set
96
+ `passClient: true` only if you need the live client.
97
+ 4. If the event is a **new** wire event, add it to `PROTOCOL_EVENTS` in
98
+ `bifrost_constants.js` and (if it is a render-node op) confirm the server added
99
+ the opcode — the client mirror `_ZRENDER_OPS` and `render_opcodes.py` must agree
100
+ (an unknown opcode logs a drift warning; see [PROTOCOL.md](PROTOCOL.md#2-render-stream--render_chunk--opcodes)).
101
+ 5. Syntax-check (ESM): `node --input-type=module --check < path/to/renderer.js`.
@@ -0,0 +1,92 @@
1
+ # Security & Trust Boundary
2
+
3
+ This client is a **thin renderer**. Keeping it thin is the security model: the less
4
+ it knows and decides, the smaller its attack surface. This document records the
5
+ boundary and the client-side hardening.
6
+
7
+ ## The boundary: what stays server-side
8
+
9
+ The client holds **none** of the following — they live in the zOS/zGuard server:
10
+
11
+ - routing / route resolution
12
+ - `.zolo` parsing or any application grammar
13
+ - RBAC decisions (the client only *displays* RBAC-filtered output the server sends)
14
+ - business logic, schemas, secrets, tokens, signing keys
15
+ - the render-event vocabulary (sent as opaque opcodes; see
16
+ [PROTOCOL.md](PROTOCOL.md#2-render-stream--render_chunk--opcodes))
17
+
18
+ The client's job is: open a socket, decode JSON events, build DOM. Anything that
19
+ would leak mechanism or grant trust is the server's responsibility. Render opcodes
20
+ are an obfuscation/compactness layer, **not** an authorization boundary.
21
+
22
+ ## DOM-XSS: centralized escaping (SSOT)
23
+
24
+ Renderers use `innerHTML` for speed, so any dynamic value interpolated into markup
25
+ must be escaped through the single source of truth in
26
+ `zSys/dom/encoding_utils.js`:
27
+
28
+ - **`escapeHtml(value)`** — escapes `& < > " '`. Safe for text and quoted-attribute
29
+ contexts. No `.replace()` chains anywhere else in the codebase.
30
+ - **`safeHref(url)`** — blocks `javascript:` / `data:` / `vbscript:` schemes
31
+ (including whitespace/control-char obfuscation), returns `#` for blocked/empty,
32
+ otherwise attribute-escapes. All `href`/`src` values go through it.
33
+
34
+ Notably, markdown link assembly in `text_renderer.js` escapes the link label and
35
+ runs the resolved href through `safeHref`, so a `[label](javascript:…)` payload can
36
+ never produce an executable link. See [RENDERERS.md](RENDERERS.md#html-escaping-is-centralized-ssot).
37
+
38
+ > The client escapes its own output, but it cannot vouch for what a zApp echoes. zApps
39
+ > that display untrusted user input are responsible for not defeating these guards.
40
+
41
+ ## Core-import origin pinning
42
+
43
+ The bootstrap dynamically `import()`s `bifrost_core.js` from
44
+ `connection_info.bifrost_core_url`. Because `connection_info` arrives over the
45
+ socket, a spoofed payload could otherwise point the import at attacker-hosted code.
46
+ `bifrost_client._loadCore` therefore:
47
+
48
+ 1. resolves the URL with `new URL(rawUrl, location.origin)` (rejects malformed),
49
+ 2. allows the import **only** from an allowlisted origin:
50
+ - the page origin (`window.location.origin`),
51
+ - the official jsDelivr CDN (`https://cdn.jsdelivr.net`, where the canonical
52
+ client ships),
53
+ - any origin you opt into via `opts.coreOriginAllowlist`.
54
+
55
+ Anything else is refused with a logged error and the core is not loaded.
56
+
57
+ ```js
58
+ // permit a custom/self-hosted CDN origin:
59
+ new BifrostClient(null, {
60
+ autoConnect: true,
61
+ coreOriginAllowlist: ['https://cdn.example.com'],
62
+ });
63
+ ```
64
+
65
+ > Loading from `https://cdn.jsdelivr.net` is allowed by default because that is the
66
+ > canonical distribution channel and the version is still chosen by *your* server.
67
+ > Self-hosting the core? Serve it from the page origin (no config needed) or add its
68
+ > origin to `coreOriginAllowlist`.
69
+
70
+ ## Session-cookie handling
71
+
72
+ `message_handler._getSessionIdFromCookie` reads `session`/`sessionid` from
73
+ `document.cookie` and attaches it to `execute_walker` requests as a best-effort
74
+ session-sync hint for the WebSocket/HTTP bridge.
75
+
76
+ - This only works when the cookie is **not** `HttpOnly`.
77
+ - It is **not** proof of identity — the server re-validates every request.
78
+ - `HttpOnly` deployments (recommended) simply get `null` here and rely on the
79
+ browser attaching the cookie to the WS handshake — the safer path.
80
+
81
+ ## Opcode-mirror drift
82
+
83
+ `_ZRENDER_OPS` mirrors the server `render_opcodes.py`. An unknown opcode is **not**
84
+ silently dropped — `_warnUnknownOpcode` logs once so a server change the client
85
+ hasn't mirrored is visible. Treat `[zRender] Unknown render opcode "…"` as "update
86
+ the client mirror / bump the client version."
87
+
88
+ ## Reporting
89
+
90
+ This is the public, open-source renderer. Report client-side issues against the
91
+ `zbifrost-client` repo. Server-side concerns (auth, RBAC, `execute_code`, the
92
+ sealed network runtime) belong to the zOS/zGuard projects.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@zolomedia/bifrost-client",
3
+ "version": "1.7.74",
4
+ "description": "Browser client for zBifrost — the WebSocket bridge that turns JSON events from a zOS server into live DOM. Thin bootstrap + server-controlled core.",
5
+ "homepage": "https://github.com/ZoloAi/zbifrost-client#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ZoloAi/zbifrost-client.git"
9
+ },
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "keywords": [
12
+ "zos",
13
+ "zbifrost",
14
+ "websocket",
15
+ "ssr",
16
+ "declarative-ui"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "release:patch": "npm version patch && npm publish"
23
+ }
24
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Prism.js language definition for zconfig
3
+ * zConfig.*.zolo - System configuration files
4
+ *
5
+ * Extends base 'zolo' language with file-type-specific patterns.
6
+ *
7
+ * Generated by zlsp/zlsp/generators/generate_prism_zolo.py
8
+ * DO NOT EDIT MANUALLY - regenerate with: python3 -m zlsp.generators.generate_prism_zolo
9
+ */
10
+
11
+ Prism.languages.zconfig = Prism.languages.extend('zolo', {
12
+ 'zconfig-special-root': {
13
+ pattern: /((?:^|\n)[ \t]*)(?:zMachine|Z[A-Z0-9_]+)(?=\s*:)/m,
14
+ alias: 'function',
15
+ lookbehind: true,
16
+ },
17
+ 'root-key': {
18
+ pattern: /(^|\n)([A-Z][a-zA-Z0-9_]*)(?=\s*(?:\([^)]+\))?[*!^~]?:)/m,
19
+ alias: 'class-name',
20
+ lookbehind: true,
21
+ },
22
+ 'zmachine-locked-section': {
23
+ pattern: /(?<=\n)[ \t]{1}(?:storage|python_runtime|launch_commands|network|display|memory|machine_identity|user_paths|gpu|cpu)(?=\s*:)/m,
24
+ alias: 'constant',
25
+ lookbehind: true,
26
+ },
27
+ 'zmachine-editable-section': {
28
+ pattern: /(?<=\n)[ \t]{1}(?:user_preferences|time_date_formatting|custom)(?=\s*:)/m,
29
+ alias: 'variable',
30
+ lookbehind: true,
31
+ },
32
+ 'zconfig-property': {
33
+ pattern: /(?<=\n)[ \t]{2,}[a-zA-Z][a-zA-Z0-9_]*(?=\s*:)/m,
34
+ alias: 'variable',
35
+ lookbehind: true,
36
+ },
37
+ 'property': null
38
+ });
39
+
40
+ // Support both ```zconfig and ```zConfig code fences
41
+ Prism.languages.zConfig = Prism.languages.zconfig;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Prism.js language definition for zenv
3
+ * zEnv.*.zolo - Environment configuration files
4
+ *
5
+ * Extends base 'zolo' language with file-type-specific patterns.
6
+ *
7
+ * Generated by zlsp/zlsp/generators/generate_prism_zolo.py
8
+ * DO NOT EDIT MANUALLY - regenerate with: python3 -m zlsp.generators.generate_prism_zolo
9
+ */
10
+
11
+ Prism.languages.zenv = Prism.languages.extend('zolo', {
12
+ 'zenv-config-root': {
13
+ pattern: /((?:^|\n)[ \t]*)(?:LOG_LEVEL|DEBUG|DEPLOYMENT)(?=\s*:)/m,
14
+ alias: 'keyword',
15
+ lookbehind: true,
16
+ },
17
+ 'zenv-z-uppercase-root': {
18
+ pattern: /((?:^|\n)[ \t]*)Z[A-Z0-9_]+(?=\s*:)/m,
19
+ alias: 'function',
20
+ lookbehind: true,
21
+ },
22
+ 'root-key': {
23
+ pattern: /(^|\n)([A-Z][a-zA-Z0-9_]*)(?=\s*(?:\([^)]+\))?[*!^~]?:)/m,
24
+ alias: 'class-name',
25
+ lookbehind: true,
26
+ },
27
+ 'znavbar-nested': {
28
+ pattern: /(?<=\n)[ \t]{1}[a-zA-Z][a-zA-Z0-9_]*(?=\s*:)/m,
29
+ alias: 'type',
30
+ lookbehind: true,
31
+ },
32
+ 'zsub-key': {
33
+ pattern: /(?<=\n)[ \t]{2,}zSub(?=\s*:)/m,
34
+ alias: 'keyword',
35
+ lookbehind: true,
36
+ },
37
+ 'zrbac-key': {
38
+ pattern: /\bzRBAC(?=\s*:)/,
39
+ alias: 'constant',
40
+ },
41
+ 'property': {
42
+ pattern: /\b[a-zA-Z][a-zA-Z0-9_]*(?=\s*(?:\([^)]+\))?[*!]?:)/,
43
+ },
44
+ 'prefix-modifier': {
45
+ pattern: /[\^~](?=[a-zA-Z][a-zA-Z0-9_]*\s*:)/,
46
+ alias: 'variable',
47
+ },
48
+ 'suffix-modifier': {
49
+ pattern: /[*!](?=:)/,
50
+ alias: 'operator',
51
+ }
52
+ });
53
+
54
+ // Insert value patterns BEFORE string-unquoted for priority
55
+ Prism.languages.insertBefore('zenv', 'string-unquoted', {
56
+ 'zpath-value': {
57
+ pattern: /[@~]\.[a-zA-Z0-9_./]+/,
58
+ alias: 'string',
59
+ }
60
+ });
61
+ Prism.languages.insertBefore('zenv', 'string-unquoted', {
62
+ 'env-constant-value': {
63
+ pattern: /\b(?:DEBUG|PROD|INFO|WARNING|ERROR|CRITICAL|SESSION|Development|Production)\b/,
64
+ alias: 'number',
65
+ }
66
+ });
67
+
68
+ // Support both ```zenv and ```zEnv code fences
69
+ Prism.languages.zEnv = Prism.languages.zenv;