@xuda.io/runtime-bundle 1.0.1436 → 1.0.1437

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.
@@ -1705,7 +1705,30 @@ func.runtime.platform.emit = function (name, data) {
1705
1705
  };
1706
1706
 
1707
1707
  // ── Platform helpers for DOM-independent resource loading ──
1708
- func.runtime.platform.load_script = function (url, type, callback) {
1708
+ func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
1709
+ if (!node?.setAttribute || !attributes) {
1710
+ return node;
1711
+ }
1712
+
1713
+ const excluded = new Set(excluded_keys || []);
1714
+ const attr_keys = Object.keys(attributes);
1715
+ for (let index = 0; index < attr_keys.length; index++) {
1716
+ const key = attr_keys[index];
1717
+ if (!key || excluded.has(key)) {
1718
+ continue;
1719
+ }
1720
+
1721
+ const value = attributes[key];
1722
+ if (value === false || typeof value === 'undefined') {
1723
+ continue;
1724
+ }
1725
+
1726
+ node.setAttribute(key, value === null ? '' : `${value}`);
1727
+ }
1728
+
1729
+ return node;
1730
+ };
1731
+ func.runtime.platform.load_script = function (url, type, callback, attributes) {
1709
1732
  const doc = func.runtime.platform.get_document();
1710
1733
  if (!doc?.createElement || !doc?.head?.appendChild) {
1711
1734
  if (callback) {
@@ -1713,19 +1736,63 @@ func.runtime.platform.load_script = function (url, type, callback) {
1713
1736
  }
1714
1737
  return;
1715
1738
  }
1739
+ const find_existing_script = function () {
1740
+ const asset_key = attributes?.['data-xuda-asset-key'];
1741
+ const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
1742
+ return scripts.find(function (script) {
1743
+ if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
1744
+ return true;
1745
+ }
1746
+ return !!(url && script.getAttribute('src') === url);
1747
+ }) || null;
1748
+ };
1749
+
1750
+ const existing_script = find_existing_script();
1751
+ if (existing_script) {
1752
+ if (callback) {
1753
+ if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
1754
+ callback();
1755
+ } else {
1756
+ existing_script.addEventListener('load', callback, { once: true });
1757
+ existing_script.addEventListener('error', callback, { once: true });
1758
+ }
1759
+ }
1760
+ return existing_script;
1761
+ }
1762
+
1716
1763
  const script = doc.createElement('script');
1717
1764
  script.src = url;
1718
1765
  if (type) script.type = type;
1719
- script.onload = callback;
1766
+ func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
1767
+ script.onload = function () {
1768
+ script.setAttribute('data-xuda-loaded', 'true');
1769
+ if (callback) {
1770
+ callback();
1771
+ }
1772
+ };
1773
+ script.onerror = function () {
1774
+ if (callback) {
1775
+ callback();
1776
+ }
1777
+ };
1720
1778
  doc.head.appendChild(script);
1779
+ return script;
1721
1780
  };
1722
- func.runtime.platform.load_css = function (href) {
1781
+ func.runtime.platform.load_css = function (href, attributes) {
1723
1782
  const doc = func.runtime.platform.get_document();
1724
1783
  if (!doc?.createElement || !doc?.head) {
1725
1784
  return;
1726
1785
  }
1727
1786
  try {
1728
- if (doc.querySelector('link[href="' + href + '"]')) return;
1787
+ const asset_key = attributes?.['data-xuda-asset-key'];
1788
+ const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
1789
+ const existing = existing_links.find(function (link) {
1790
+ if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
1791
+ return true;
1792
+ }
1793
+ return !!(href && link.getAttribute('href') === href);
1794
+ });
1795
+ if (existing) return existing;
1729
1796
  } catch (err) {
1730
1797
  return;
1731
1798
  }
@@ -1733,7 +1800,9 @@ func.runtime.platform.load_css = function (href) {
1733
1800
  link.rel = 'stylesheet';
1734
1801
  link.type = 'text/css';
1735
1802
  link.href = href;
1803
+ func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
1736
1804
  doc.head.insertBefore(link, doc.head.firstChild);
1805
+ return link;
1737
1806
  };
1738
1807
  func.runtime.platform.remove_js_css = function (filename, filetype) {
1739
1808
  const doc = func.runtime.platform.get_document();
@@ -2024,6 +2093,147 @@ func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_q
2024
2093
  delete registry_entry.promise_queue[promise_queue_id];
2025
2094
  return true;
2026
2095
  };
2096
+ func.runtime.render.clone_runtime_options = function (value) {
2097
+ if (typeof structuredClone === 'function') {
2098
+ try {
2099
+ return structuredClone(value);
2100
+ } catch (_) {}
2101
+ }
2102
+
2103
+ if (Array.isArray(value)) {
2104
+ return value.map(function (item) {
2105
+ return func.runtime.render.clone_runtime_options(item);
2106
+ });
2107
+ }
2108
+
2109
+ if (value && typeof value === 'object') {
2110
+ const cloned = {};
2111
+ const keys = Object.keys(value);
2112
+ for (let index = 0; index < keys.length; index++) {
2113
+ const key = keys[index];
2114
+ cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
2115
+ }
2116
+ return cloned;
2117
+ }
2118
+
2119
+ return value;
2120
+ };
2121
+ func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
2122
+ const options = raw_options || {};
2123
+ let app_computing_mode = options.app_computing_mode || '';
2124
+ let app_render_mode = options.app_render_mode || '';
2125
+ let app_client_activation = options.app_client_activation || '';
2126
+ let ssr_payload = options.ssr_payload || null;
2127
+
2128
+ if (typeof ssr_payload === 'string') {
2129
+ try {
2130
+ ssr_payload = JSON.parse(ssr_payload);
2131
+ } catch (_) {
2132
+ ssr_payload = null;
2133
+ }
2134
+ }
2135
+
2136
+ if (ssr_payload && typeof ssr_payload === 'object') {
2137
+ ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
2138
+ }
2139
+
2140
+ if (!app_computing_mode) {
2141
+ if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
2142
+ app_computing_mode = 'server';
2143
+ } else {
2144
+ app_computing_mode = 'main';
2145
+ }
2146
+ }
2147
+
2148
+ switch (app_computing_mode) {
2149
+ case 'main':
2150
+ app_render_mode = 'csr';
2151
+ app_client_activation = 'none';
2152
+ break;
2153
+
2154
+ case 'worker':
2155
+ app_render_mode = 'csr';
2156
+ app_client_activation = 'none';
2157
+ break;
2158
+
2159
+ default:
2160
+ app_computing_mode = 'server';
2161
+ if (app_render_mode !== 'ssr_full') {
2162
+ app_render_mode = 'ssr_first_page';
2163
+ }
2164
+ app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
2165
+ break;
2166
+ }
2167
+
2168
+ if (ssr_payload && typeof ssr_payload === 'object') {
2169
+ if (!ssr_payload.app_render_mode) {
2170
+ ssr_payload.app_render_mode = app_render_mode;
2171
+ }
2172
+ if (!ssr_payload.app_client_activation) {
2173
+ ssr_payload.app_client_activation = app_client_activation;
2174
+ }
2175
+ if (!ssr_payload.app_computing_mode) {
2176
+ ssr_payload.app_computing_mode = app_computing_mode;
2177
+ }
2178
+ }
2179
+
2180
+ return {
2181
+ app_computing_mode,
2182
+ app_render_mode,
2183
+ app_client_activation,
2184
+ ssr_payload,
2185
+ };
2186
+ };
2187
+ func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
2188
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
2189
+ target.app_computing_mode = normalized.app_computing_mode;
2190
+ target.app_render_mode = normalized.app_render_mode;
2191
+ target.app_client_activation = normalized.app_client_activation;
2192
+ target.ssr_payload = normalized.ssr_payload;
2193
+ return normalized;
2194
+ };
2195
+ func.runtime.render.is_server_render_mode = function (target = {}) {
2196
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2197
+ return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
2198
+ };
2199
+ func.runtime.render.is_takeover_mode = function (target = {}) {
2200
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2201
+ return normalized.app_client_activation === 'takeover';
2202
+ };
2203
+ func.runtime.render.is_hydration_mode = function (target = {}) {
2204
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2205
+ return normalized.app_client_activation === 'hydrate';
2206
+ };
2207
+ func.runtime.render.get_ssr_payload = function (target = {}) {
2208
+ if (target?.opt?.ssr_payload) {
2209
+ return target.opt.ssr_payload;
2210
+ }
2211
+ if (target?.ssr_payload) {
2212
+ return target.ssr_payload;
2213
+ }
2214
+ const win = func.runtime.platform.get_window();
2215
+ return win?.__XUDA_SSR__ || null;
2216
+ };
2217
+ func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
2218
+ const session = SESSION_OBJ?.[SESSION_ID];
2219
+ const payload = func.runtime.render.get_ssr_payload(session);
2220
+ if (!payload || payload._consumed) {
2221
+ return false;
2222
+ }
2223
+ if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
2224
+ return false;
2225
+ }
2226
+ return true;
2227
+ };
2228
+ func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
2229
+ const session = SESSION_OBJ?.[SESSION_ID];
2230
+ const payload = func.runtime.render.get_ssr_payload(session);
2231
+ if (!payload || typeof payload !== 'object') {
2232
+ return false;
2233
+ }
2234
+ payload._consumed = true;
2235
+ return true;
2236
+ };
2027
2237
  func.runtime.render.get_root_data_system = function (SESSION_ID) {
2028
2238
  return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
2029
2239
  };
@@ -2427,10 +2637,10 @@ func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
2427
2637
  await func.utils.load_js_on_demand(normalized_resource.src);
2428
2638
  break;
2429
2639
  case 'css':
2430
- await func.utils.load_js_on_demand(normalized_resource.src);
2640
+ func.runtime.platform.load_css(normalized_resource.src);
2431
2641
  break;
2432
2642
  case 'module':
2433
- func.utils.load_js_on_demand(normalized_resource.src, 'module');
2643
+ await func.utils.load_js_on_demand(normalized_resource.src, 'module');
2434
2644
  break;
2435
2645
  default:
2436
2646
  await func.utils.load_js_on_demand(normalized_resource.src);
@@ -3743,30 +3953,715 @@ func.common.get_data_from_websocket = async function (SESSION_ID, serviceP, data
3743
3953
  // // The .toString(36) method handles the conversion to an alphanumeric representation (0-9, a-z).
3744
3954
  // const base36Hash = bigInt.toString(36);
3745
3955
 
3746
- // // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
3747
- // // For a 64-bit integer, the Base36 representation will be about 13 characters long,
3748
- // // so slicing is a reliable way to get a fixed length.
3749
- // const shortHash = base36Hash.slice(0, 10);
3956
+ // // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
3957
+ // // For a 64-bit integer, the Base36 representation will be about 13 characters long,
3958
+ // // so slicing is a reliable way to get a fixed length.
3959
+ // const shortHash = base36Hash.slice(0, 10);
3960
+
3961
+ // // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
3962
+ // // This ensures the output is always exactly 10 characters long.
3963
+ // return shortHash.padStart(10, '0');
3964
+ // };
3965
+
3966
+ func.common.fastHash = function (inputString) {
3967
+ let hash = 0x811c9dc5; // FNV offset basis
3968
+
3969
+ for (let i = 0; i < inputString.length; i++) {
3970
+ hash ^= inputString.charCodeAt(i);
3971
+ // FNV prime multiplication with 32-bit overflow
3972
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
3973
+ }
3974
+
3975
+ // Convert to base36 and pad to 10 characters
3976
+ return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
3977
+ };
3978
+
3979
+ glb.new_xu_render = false;
3980
+ func.runtime = func.runtime || {};
3981
+ func.runtime.ui = func.runtime.ui || {};
3982
+ func.runtime.render = func.runtime.render || {};
3983
+ func.runtime.widgets = func.runtime.widgets || {};
3984
+
3985
+ // Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
3986
+
3987
+ func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
3988
+ func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
3989
+
3990
+ func.runtime.render.safe_clone_tree_value = function (value) {
3991
+ if (typeof structuredClone === 'function') {
3992
+ try {
3993
+ return structuredClone(value);
3994
+ } catch (_) {
3995
+ // Fall through to the recursive clone below.
3996
+ }
3997
+ }
3998
+
3999
+ if (Array.isArray(value)) {
4000
+ return value.map(function (item) {
4001
+ return func.runtime.render.safe_clone_tree_value(item);
4002
+ });
4003
+ }
4004
+
4005
+ if (value && typeof value === 'object') {
4006
+ const cloned = {};
4007
+ const keys = Object.keys(value);
4008
+ for (let index = 0; index < keys.length; index++) {
4009
+ const key = keys[index];
4010
+ cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
4011
+ }
4012
+ return cloned;
4013
+ }
4014
+
4015
+ return value;
4016
+ };
4017
+ func.runtime.render.sort_tree_debug_value = function (value) {
4018
+ if (Array.isArray(value)) {
4019
+ return value.map(function (item) {
4020
+ return func.runtime.render.sort_tree_debug_value(item);
4021
+ });
4022
+ }
4023
+
4024
+ if (value && typeof value === 'object') {
4025
+ const sorted = {};
4026
+ const keys = Object.keys(value).sort();
4027
+ for (let index = 0; index < keys.length; index++) {
4028
+ const key = keys[index];
4029
+ sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
4030
+ }
4031
+ return sorted;
4032
+ }
4033
+
4034
+ return value;
4035
+ };
4036
+ func.runtime.render.is_tree_node = function (nodeP) {
4037
+ return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
4038
+ };
4039
+ func.runtime.render.get_tree_source_node = function (nodeP) {
4040
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4041
+ return nodeP || null;
4042
+ }
4043
+ return nodeP?.meta?.source_node || null;
4044
+ };
4045
+ func.runtime.render.get_tree_source_snapshot = function (nodeP) {
4046
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4047
+ return func.runtime.render.safe_clone_tree_value(nodeP);
4048
+ }
4049
+ return nodeP?.meta?.source_snapshot || null;
4050
+ };
4051
+ func.runtime.render.get_tree_node_kind = function (nodeP) {
4052
+ const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
4053
+ const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
4054
+
4055
+ if (tag_name === 'xu-widget') return 'widget';
4056
+ if (tag_name === 'xu-single-view') return 'single_view';
4057
+ if (tag_name === 'xu-multi-view') return 'multi_view';
4058
+ if (tag_name === 'xu-panel') return 'panel';
4059
+ if (tag_name === 'xu-teleport') return 'teleport';
4060
+ if (tag_name === 'xurender') return 'placeholder';
4061
+ if (tag_name === '#text' || node_type === 'text') return 'text';
4062
+ if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
4063
+ return 'element';
4064
+ };
4065
+ func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
4066
+ if (nodeP?.id) {
4067
+ return nodeP.id;
4068
+ }
4069
+ if (nodeP?.id_org) {
4070
+ return nodeP.id_org;
4071
+ }
4072
+ const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
4073
+ return `tree-node-${normalized_path}`;
4074
+ };
4075
+ func.runtime.render.get_tree_controls = function (attributes) {
4076
+ const attrs = attributes || {};
4077
+ const get_first_defined = function (keys) {
4078
+ for (let index = 0; index < keys.length; index++) {
4079
+ const key = keys[index];
4080
+ if (Object.prototype.hasOwnProperty.call(attrs, key)) {
4081
+ return attrs[key];
4082
+ }
4083
+ }
4084
+ return null;
4085
+ };
4086
+ return {
4087
+ xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
4088
+ xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
4089
+ xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
4090
+ };
4091
+ };
4092
+ func.runtime.render.get_tree_node_capabilities = async function (options) {
4093
+ const attributes = options?.attributes || {};
4094
+ const plugin_name = attributes['xu-widget'];
4095
+ if (!plugin_name) {
4096
+ return null;
4097
+ }
4098
+
4099
+ const cache = func.runtime.render._tree_widget_capability_cache;
4100
+ if (cache[plugin_name]) {
4101
+ return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
4102
+ }
4103
+
4104
+ let capabilities = {
4105
+ browser: true,
4106
+ headless: false,
4107
+ };
4108
+
4109
+ try {
4110
+ if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
4111
+ const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
4112
+ const definition = await func.runtime.widgets.get_definition(widget_context);
4113
+ capabilities = func.runtime.widgets.normalize_capabilities(definition);
4114
+ }
4115
+ } catch (_) {
4116
+ // Keep the safe browser-only default when the widget definition is unavailable.
4117
+ }
4118
+
4119
+ cache[plugin_name] = capabilities;
4120
+ return func.runtime.render.safe_clone_tree_value(capabilities);
4121
+ };
4122
+ func.runtime.render.ensure_tree_node = async function (options) {
4123
+ if (!options?.nodeP) {
4124
+ return null;
4125
+ }
4126
+ if (func.runtime.render.is_tree_node(options.nodeP)) {
4127
+ return options.nodeP;
4128
+ }
4129
+ return await func.runtime.render.build_tree(options);
4130
+ };
4131
+ func.runtime.render.build_tree = async function (options) {
4132
+ if (Array.isArray(options?.nodeP)) {
4133
+ return await func.runtime.render.build_tree_list({
4134
+ ...options,
4135
+ nodesP: options.nodeP,
4136
+ });
4137
+ }
4138
+
4139
+ const nodeP = options?.nodeP;
4140
+ if (!nodeP) {
4141
+ return null;
4142
+ }
4143
+ if (func.runtime.render.is_tree_node(nodeP)) {
4144
+ return nodeP;
4145
+ }
4146
+
4147
+ const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
4148
+ const tree_path = pathP.length ? pathP.slice() : [0];
4149
+ const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
4150
+ if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined') {
4151
+ attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
4152
+ }
4153
+
4154
+ const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
4155
+ SESSION_ID: options?.SESSION_ID,
4156
+ paramsP: options?.paramsP,
4157
+ attributes,
4158
+ });
4159
+ const children = [];
4160
+ const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
4161
+ const parent_tree_id = tree_path.join('.');
4162
+
4163
+ for (let index = 0; index < child_nodes.length; index++) {
4164
+ const child_tree = await func.runtime.render.build_tree({
4165
+ ...options,
4166
+ nodeP: child_nodes[index],
4167
+ pathP: tree_path.concat(index),
4168
+ parent_tree_id: parent_tree_id,
4169
+ keyP: index,
4170
+ parent_nodeP: nodeP,
4171
+ });
4172
+ if (child_tree) {
4173
+ children.push(child_tree);
4174
+ }
4175
+ }
4176
+
4177
+ const tree = {
4178
+ contract: func.runtime.render.TREE_CONTRACT_VERSION,
4179
+ id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
4180
+ xu_tree_id: `tree.${tree_path.join('.')}`,
4181
+ kind: func.runtime.render.get_tree_node_kind(nodeP),
4182
+ tagName: nodeP.tagName || null,
4183
+ attributes,
4184
+ text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
4185
+ content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
4186
+ children,
4187
+ meta: {
4188
+ tree_id: tree_path.join('.'),
4189
+ path: tree_path,
4190
+ parent_tree_id: options?.parent_tree_id || null,
4191
+ key: typeof options?.keyP === 'undefined' ? null : options.keyP,
4192
+ recordid: nodeP?.recordid || null,
4193
+ dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
4194
+ iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
4195
+ controls: func.runtime.render.get_tree_controls(attributes),
4196
+ capabilities: widget_capabilities,
4197
+ widget: attributes['xu-widget']
4198
+ ? {
4199
+ plugin_name: attributes['xu-widget'],
4200
+ method: attributes['xu-method'] || '_default',
4201
+ capabilities: widget_capabilities,
4202
+ }
4203
+ : null,
4204
+ source_node_id: nodeP?.id || nodeP?.id_org || null,
4205
+ source_node: nodeP,
4206
+ source_snapshot: func.runtime.ui?.get_node_snapshot
4207
+ ? func.runtime.ui.get_node_snapshot(nodeP)
4208
+ : func.runtime.render.safe_clone_tree_value(nodeP),
4209
+ },
4210
+ };
4211
+
4212
+ return tree;
4213
+ };
4214
+ func.runtime.render.build_tree_list = async function (options) {
4215
+ const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
4216
+ const trees = [];
4217
+
4218
+ for (let index = 0; index < nodes.length; index++) {
4219
+ const tree = await func.runtime.render.build_tree({
4220
+ ...options,
4221
+ nodeP: nodes[index],
4222
+ pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
4223
+ keyP: index,
4224
+ });
4225
+ if (tree) {
4226
+ trees.push(tree);
4227
+ }
4228
+ }
4229
+
4230
+ return trees;
4231
+ };
4232
+ func.runtime.render.sanitize_tree_for_debug = function (treeP) {
4233
+ if (Array.isArray(treeP)) {
4234
+ return treeP.map(function (child) {
4235
+ return func.runtime.render.sanitize_tree_for_debug(child);
4236
+ });
4237
+ }
4238
+
4239
+ if (!func.runtime.render.is_tree_node(treeP)) {
4240
+ return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
4241
+ }
4242
+
4243
+ return {
4244
+ contract: treeP.contract,
4245
+ id: treeP.id,
4246
+ xu_tree_id: treeP.xu_tree_id || null,
4247
+ kind: treeP.kind,
4248
+ tagName: treeP.tagName,
4249
+ attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
4250
+ text: treeP.text,
4251
+ content: treeP.content,
4252
+ children: treeP.children.map(function (child) {
4253
+ return func.runtime.render.sanitize_tree_for_debug(child);
4254
+ }),
4255
+ meta: {
4256
+ tree_id: treeP.meta?.tree_id || null,
4257
+ path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
4258
+ parent_tree_id: treeP.meta?.parent_tree_id || null,
4259
+ key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
4260
+ recordid: treeP.meta?.recordid || null,
4261
+ dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
4262
+ iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
4263
+ controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
4264
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
4265
+ widget: treeP.meta?.widget
4266
+ ? {
4267
+ plugin_name: treeP.meta.widget.plugin_name,
4268
+ method: treeP.meta.widget.method,
4269
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
4270
+ }
4271
+ : null,
4272
+ source_node_id: treeP.meta?.source_node_id || null,
4273
+ },
4274
+ };
4275
+ };
4276
+ func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
4277
+ return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
4278
+ };
4279
+ func.runtime = func.runtime || {};
4280
+ func.runtime.ui = func.runtime.ui || {};
4281
+ func.runtime.render = func.runtime.render || {};
4282
+ func.runtime.widgets = func.runtime.widgets || {};
4283
+
4284
+ // Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
4285
+
4286
+ func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
4287
+ area: true,
4288
+ base: true,
4289
+ br: true,
4290
+ col: true,
4291
+ embed: true,
4292
+ hr: true,
4293
+ img: true,
4294
+ input: true,
4295
+ link: true,
4296
+ meta: true,
4297
+ param: true,
4298
+ source: true,
4299
+ track: true,
4300
+ wbr: true,
4301
+ };
4302
+ func.runtime.render.escape_html = function (value) {
4303
+ return `${value ?? ''}`
4304
+ .replaceAll('&', '&amp;')
4305
+ .replaceAll('<', '&lt;')
4306
+ .replaceAll('>', '&gt;')
4307
+ .replaceAll('"', '&quot;')
4308
+ .replaceAll("'", '&#039;');
4309
+ };
4310
+ func.runtime.render.escape_html_attribute = function (value) {
4311
+ return func.runtime.render.escape_html(value);
4312
+ };
4313
+ func.runtime.render.is_html_void_tag = function (tag_name) {
4314
+ return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
4315
+ };
4316
+ func.runtime.render.is_falsey_render_value = function (value) {
4317
+ if (value === false || value === null || typeof value === 'undefined') {
4318
+ return true;
4319
+ }
4320
+ if (typeof value === 'number') {
4321
+ return value === 0;
4322
+ }
4323
+ if (typeof value === 'string') {
4324
+ const normalized = value.trim().toLowerCase();
4325
+ return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
4326
+ }
4327
+ return false;
4328
+ };
4329
+ func.runtime.render.should_render_tree_node = function (treeP) {
4330
+ const controls = treeP?.meta?.controls || {};
4331
+ if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
4332
+ return false;
4333
+ }
4334
+ if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
4335
+ return false;
4336
+ }
4337
+ return true;
4338
+ };
4339
+ func.runtime.render.is_tree_control_attribute = function (key) {
4340
+ if (!key) {
4341
+ return false;
4342
+ }
4343
+ return (
4344
+ key.startsWith('xu-exp:') ||
4345
+ key === 'xu-widget' ||
4346
+ key === 'xu-method' ||
4347
+ key === 'xu-for' ||
4348
+ key === 'xu-for-key' ||
4349
+ key === 'xu-for-val' ||
4350
+ key === 'xu-if' ||
4351
+ key === 'xu-render' ||
4352
+ key === 'xu-bind' ||
4353
+ key === 'xu-content' ||
4354
+ key === 'xu-text' ||
4355
+ key === 'xu-html' ||
4356
+ key === 'xu-show' ||
4357
+ key === 'xu-panel-program' ||
4358
+ key === 'xu-teleport'
4359
+ );
4360
+ };
4361
+ func.runtime.render.get_string_renderer_tag_name = function (treeP) {
4362
+ switch (treeP?.kind) {
4363
+ case 'widget':
4364
+ case 'single_view':
4365
+ case 'multi_view':
4366
+ case 'panel':
4367
+ case 'teleport':
4368
+ return 'div';
4369
+ case 'placeholder':
4370
+ return null;
4371
+ case 'text':
4372
+ return null;
4373
+ default:
4374
+ return treeP?.tagName || 'div';
4375
+ }
4376
+ };
4377
+ func.runtime.render.get_tree_terminal_content = function (treeP) {
4378
+ const attributes = treeP?.attributes || {};
4379
+ if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
4380
+ return {
4381
+ value: `${attributes['xu-html']}`,
4382
+ mode: 'html',
4383
+ };
4384
+ }
4385
+ if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
4386
+ return {
4387
+ value: `${attributes['xu-content']}`,
4388
+ mode: 'html',
4389
+ };
4390
+ }
4391
+ if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
4392
+ return {
4393
+ value: `${attributes['xu-text']}`,
4394
+ mode: 'text',
4395
+ };
4396
+ }
4397
+ if (treeP?.kind === 'text') {
4398
+ return {
4399
+ value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
4400
+ mode: 'text',
4401
+ };
4402
+ }
4403
+ return null;
4404
+ };
4405
+ func.runtime.render.render_tree_terminal_content = function (treeP) {
4406
+ const terminal = func.runtime.render.get_tree_terminal_content(treeP);
4407
+ if (!terminal) {
4408
+ return null;
4409
+ }
4410
+ if (terminal.mode === 'html') {
4411
+ return terminal.value;
4412
+ }
4413
+ return func.runtime.render.escape_html(terminal.value);
4414
+ };
4415
+ func.runtime.render.get_widget_fallback_markup = function (treeP) {
4416
+ const widget_meta = treeP?.meta?.widget || {};
4417
+ const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
4418
+ return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
4419
+ };
4420
+ func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
4421
+ const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
4422
+ const attr_pairs = [];
4423
+ const keys = Object.keys(attributes);
4424
+
4425
+ for (let index = 0; index < keys.length; index++) {
4426
+ const key = keys[index];
4427
+ if (func.runtime.render.is_tree_control_attribute(key)) {
4428
+ continue;
4429
+ }
4430
+ const value = attributes[key];
4431
+ if (value === false || value === null || typeof value === 'undefined') {
4432
+ continue;
4433
+ }
4434
+ if (value === true) {
4435
+ attr_pairs.push(key);
4436
+ continue;
4437
+ }
4438
+ const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
4439
+ attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
4440
+ }
4441
+
4442
+ attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
4443
+ attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
4444
+ attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
4445
+
4446
+ if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
4447
+ attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
4448
+ attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
4449
+ attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
4450
+ }
4451
+
4452
+ if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
4453
+ attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
4454
+ }
4455
+
4456
+ if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
4457
+ attr_pairs.push('data-xuda-xu-for="pending"');
4458
+ }
4459
+
4460
+ return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
4461
+ };
4462
+ func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
4463
+ if (!Array.isArray(treeP?.children) || !treeP.children.length) {
4464
+ return '';
4465
+ }
4466
+ let html = '';
4467
+ for (let index = 0; index < treeP.children.length; index++) {
4468
+ html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
4469
+ ...renderer_context,
4470
+ parent_tree: treeP,
4471
+ });
4472
+ }
4473
+ return html;
4474
+ };
4475
+ func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
4476
+ if (!treeP) {
4477
+ return '';
4478
+ }
4479
+ if (Array.isArray(treeP)) {
4480
+ let html = '';
4481
+ for (let index = 0; index < treeP.length; index++) {
4482
+ html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
4483
+ }
4484
+ return html;
4485
+ }
4486
+
4487
+ const ensured_tree = await func.runtime.render.ensure_tree_node({
4488
+ SESSION_ID: renderer_context?.SESSION_ID,
4489
+ nodeP: treeP,
4490
+ paramsP: renderer_context?.paramsP,
4491
+ parent_infoP: renderer_context?.parent_infoP,
4492
+ keyP: renderer_context?.keyP,
4493
+ parent_nodeP: renderer_context?.parent_nodeP,
4494
+ });
4495
+
4496
+ if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
4497
+ return '';
4498
+ }
4499
+
4500
+ if (ensured_tree.kind === 'placeholder') {
4501
+ if (renderer_context?.include_placeholders) {
4502
+ return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
4503
+ }
4504
+ return '';
4505
+ }
4506
+
4507
+ if (ensured_tree.kind === 'text') {
4508
+ return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
4509
+ }
4510
+
4511
+ const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
4512
+ if (!tag_name || tag_name.toLowerCase() === 'script') {
4513
+ return '';
4514
+ }
4515
+
4516
+ const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
4517
+ const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
4518
+ let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
4519
+
4520
+ if (ensured_tree.kind === 'widget' && !children_html) {
4521
+ children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
4522
+ }
4523
+
4524
+ if (func.runtime.render.is_html_void_tag(tag_name)) {
4525
+ return `<${tag_name}${attributes}>`;
4526
+ }
4527
+
4528
+ return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
4529
+ };
4530
+ func.runtime.render.render_to_string = async function (options = {}) {
4531
+ const treeP = await func.runtime.render.ensure_tree_node({
4532
+ SESSION_ID: options.SESSION_ID,
4533
+ nodeP: options.treeP || options.nodeP,
4534
+ paramsP: options.paramsP,
4535
+ parent_infoP: options.parent_infoP,
4536
+ keyP: options.keyP,
4537
+ parent_nodeP: options.parent_nodeP,
4538
+ });
4539
+
4540
+ return await func.runtime.render.render_tree_to_string(treeP, options);
4541
+ };
4542
+ func.runtime.render.get_server_render_mode = function (options = {}) {
4543
+ const normalized = func.runtime.render.normalize_runtime_bootstrap({
4544
+ app_computing_mode: options.app_computing_mode,
4545
+ app_render_mode: options.app_render_mode,
4546
+ app_client_activation: options.app_client_activation,
4547
+ });
4548
+
4549
+ return normalized;
4550
+ };
4551
+ func.runtime.render.build_server_render_params = async function (options = {}) {
4552
+ const SESSION_ID = options.SESSION_ID;
4553
+ const prog_id = options.prog_id;
4554
+ const dsSessionP = options.dsSessionP;
4555
+ const _session = SESSION_OBJ?.[SESSION_ID] || {};
4556
+ const _ds = _session?.DS_GLB?.[dsSessionP] || {};
4557
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4558
+
4559
+ if (!viewDoc?.properties) {
4560
+ throw new Error(`view document not found for ${prog_id}`);
4561
+ }
3750
4562
 
3751
- // // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
3752
- // // This ensures the output is always exactly 10 characters long.
3753
- // return shortHash.padStart(10, '0');
3754
- // };
4563
+ const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
4564
+ const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
4565
+ const paramsP = {
4566
+ ...base_params,
4567
+ prog_id,
4568
+ sourceScreenP: null,
4569
+ $callingContainerP: null,
4570
+ triggerIdP: null,
4571
+ callingDataSource_objP: _ds,
4572
+ rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
4573
+ renderType: viewDoc.properties?.renderType,
4574
+ parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
4575
+ source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
4576
+ is_panelP: false,
4577
+ screen_type: options.screen_type || base_params.screen_type || 'render_string',
4578
+ screenInfo: viewDoc,
4579
+ call_screen_propertiesP: base_params.call_screen_propertiesP,
4580
+ parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
4581
+ parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
4582
+ dsSessionP,
4583
+ screenId,
4584
+ containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
4585
+ };
3755
4586
 
3756
- func.common.fastHash = function (inputString) {
3757
- let hash = 0x811c9dc5; // FNV offset basis
4587
+ if (_ds) {
4588
+ _ds.screen_params = paramsP;
4589
+ }
3758
4590
 
3759
- for (let i = 0; i < inputString.length; i++) {
3760
- hash ^= inputString.charCodeAt(i);
3761
- // FNV prime multiplication with 32-bit overflow
3762
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
4591
+ return paramsP;
4592
+ };
4593
+ func.runtime.render.build_prog_tree = async function (options = {}) {
4594
+ const SESSION_ID = options.SESSION_ID;
4595
+ const prog_id = options.prog_id;
4596
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4597
+
4598
+ if (!viewDoc?.progUi?.length) {
4599
+ throw new Error(`progUi not found for ${prog_id}`);
3763
4600
  }
3764
4601
 
3765
- // Convert to base36 and pad to 10 characters
3766
- return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
4602
+ const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
4603
+ ...options,
4604
+ SESSION_ID,
4605
+ prog_id,
4606
+ viewDoc,
4607
+ }));
4608
+ const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
4609
+ const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
4610
+ const tree = await func.runtime.render.build_tree({
4611
+ SESSION_ID,
4612
+ nodeP: root_node,
4613
+ paramsP,
4614
+ });
4615
+
4616
+ return {
4617
+ tree,
4618
+ paramsP,
4619
+ viewDoc,
4620
+ };
3767
4621
  };
4622
+ func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
4623
+ const runtime_profile = func.runtime.render.get_server_render_mode(options);
4624
+ return {
4625
+ contract: 'xuda.ssr.v1',
4626
+ prog_id: options.prog_id,
4627
+ screenId: render_program.paramsP.screenId,
4628
+ containerId: render_program.paramsP.containerIdP,
4629
+ app_computing_mode: runtime_profile.app_computing_mode,
4630
+ app_render_mode: runtime_profile.app_render_mode,
4631
+ app_client_activation: runtime_profile.app_client_activation,
4632
+ tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
4633
+ };
4634
+ };
4635
+ func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
4636
+ const payload = func.runtime.render.build_ssr_payload(render_program, options);
4637
+ const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
4638
+ const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
4639
+ const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
4640
+
4641
+ return `<div data-xuda-ssr-embed="true" class="xu_embed_div"><div id="${screenId}" class="xu_embed_container" data-xuda-ssr-screen="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;"><div id="${containerId}" data-xuda-ssr-root-frame="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;">${html}</div></div></div>`;
4642
+ };
4643
+ func.runtime.render.render_prog_to_string = async function (options = {}) {
4644
+ const render_program = await func.runtime.render.build_prog_tree(options);
4645
+ const html = await func.runtime.render.render_to_string({
4646
+ ...options,
4647
+ SESSION_ID: options.SESSION_ID,
4648
+ treeP: render_program.tree,
4649
+ paramsP: render_program.paramsP,
4650
+ });
4651
+ const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
4652
+ const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
3768
4653
 
3769
- glb.new_xu_render = false;
4654
+ return {
4655
+ prog_id: options.prog_id,
4656
+ dsSessionP: render_program.paramsP.dsSessionP,
4657
+ screenId: render_program.paramsP.screenId,
4658
+ html,
4659
+ screen_html,
4660
+ tree_json: func.runtime.render.serialize_tree(render_program.tree),
4661
+ paramsP: render_program.paramsP,
4662
+ ssr_payload,
4663
+ };
4664
+ };
3770
4665
  func.runtime = func.runtime || {};
3771
4666
  func.runtime.platform = func.runtime.platform || {};
3772
4667
 
@@ -9156,6 +10051,15 @@ func.runtime.ui.ensure_embed_container = function (SESSION_ID) {
9156
10051
  const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
9157
10052
  let $embed_container = func.runtime.ui.find_by_selector($root_element, `#embed_${SESSION_ID}`, true);
9158
10053
 
10054
+ if (!$embed_container.length) {
10055
+ const $ssr_embed_container = func.runtime.ui.find_by_selector($root_element, `[data-xuda-ssr-embed="true"]`, true);
10056
+ const ssr_embed_node = func.runtime.ui.get_first_node($ssr_embed_container);
10057
+ if (ssr_embed_node) {
10058
+ ssr_embed_node.id = 'embed_' + SESSION_ID;
10059
+ $embed_container = func.runtime.ui._wrap_matches([ssr_embed_node]);
10060
+ }
10061
+ }
10062
+
9159
10063
  if (!$embed_container.length) {
9160
10064
  const embed_node = document.createElement('div');
9161
10065
  embed_node.id = 'embed_' + SESSION_ID;
@@ -9191,12 +10095,44 @@ func.runtime.ui.get_root_tag_name = function () {
9191
10095
  }
9192
10096
  return root_tag_name;
9193
10097
  };
10098
+ func.runtime.ui.find_ssr_screen_host = function ($container, screenId, containerId) {
10099
+ const container_node = func.runtime.ui.get_first_node($container);
10100
+ if (!container_node || !screenId) {
10101
+ return null;
10102
+ }
10103
+
10104
+ const dialog_node =
10105
+ container_node.querySelector?.(`#${CSS?.escape ? CSS.escape(screenId) : screenId}`) ||
10106
+ container_node.querySelector?.(`[data-xuda-ssr-screen-id="${screenId}"]`);
10107
+ if (!dialog_node) {
10108
+ return null;
10109
+ }
10110
+
10111
+ const root_frame_node =
10112
+ (containerId ? dialog_node.querySelector?.(`#${CSS?.escape ? CSS.escape(containerId) : containerId}`) : null) ||
10113
+ dialog_node.querySelector?.('[data-xuda-ssr-root-frame="true"]');
10114
+ if (!root_frame_node) {
10115
+ return null;
10116
+ }
10117
+
10118
+ return {
10119
+ $dialogDiv: func.runtime.ui._wrap_matches([dialog_node]),
10120
+ $rootFrame: func.runtime.ui._wrap_matches([root_frame_node]),
10121
+ reused_ssr_host: true,
10122
+ };
10123
+ };
9194
10124
  func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params, $callingContainerP, screenId) {
9195
10125
  var $dialogDiv;
9196
10126
  var $rootFrame;
10127
+ let reused_ssr_host = false;
9197
10128
 
9198
10129
  switch (screen_type) {
9199
10130
  case 'embed': {
10131
+ const ssr_host = func.runtime.ui.find_ssr_screen_host($callingContainerP, screenId, params?.containerIdP);
10132
+ if (ssr_host) {
10133
+ return ssr_host;
10134
+ }
10135
+
9200
10136
  const dialogNode = document.createElement('div');
9201
10137
  dialogNode.id = screenId;
9202
10138
  dialogNode.setAttribute('ui_engine', UI_FRAMEWORK_INSTALLED);
@@ -9246,6 +10182,7 @@ func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params,
9246
10182
  return {
9247
10183
  $dialogDiv,
9248
10184
  $rootFrame,
10185
+ reused_ssr_host,
9249
10186
  };
9250
10187
  };
9251
10188
  func.runtime.ui.find_xu_ui_in_root = function (SESSION_ID, xu_ui_id) {
@@ -9810,6 +10747,11 @@ func.runtime.ui.build_container_xu_data = function (options) {
9810
10747
  func.runtime.ui.apply_container_meta = function ($div, options) {
9811
10748
  const div_node = func.runtime.ui.get_first_node($div);
9812
10749
  func.runtime.ui.set_attr(div_node, 'xu-ui-id', options.ui_id);
10750
+ func.runtime.ui.set_attr(div_node, 'data-xuda-kind', options.treeP?.kind || options.nodeP?.tagName || 'element');
10751
+ func.runtime.ui.set_attr(div_node, 'data-xuda-node-id', options.nodeP?.id || options.nodeP?.id_org || '');
10752
+ if (options.treeP?.meta?.tree_id !== null && typeof options.treeP?.meta?.tree_id !== 'undefined') {
10753
+ func.runtime.ui.set_attr(div_node, 'data-xuda-tree-id', options.treeP.meta.tree_id);
10754
+ }
9813
10755
  const xuData = func.runtime.ui.build_container_xu_data(options);
9814
10756
  if (options.parent_infoP?.iterate_info) {
9815
10757
  xuData.iterate_info = options.parent_infoP.iterate_info;
@@ -9904,6 +10846,38 @@ func.runtime.ui.create_container_element = function (div_typeP, attr_str, prop,
9904
10846
  }
9905
10847
  return func.runtime.ui.create_element(div, attr_str);
9906
10848
  };
10849
+ func.runtime.ui.find_hydration_candidate = function (options) {
10850
+ if (!func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID])) {
10851
+ return null;
10852
+ }
10853
+ if (!func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
10854
+ return null;
10855
+ }
10856
+ if (options.is_placeholder || !options.treeP?.meta?.tree_id) {
10857
+ return null;
10858
+ }
10859
+
10860
+ const append_node = func.runtime.ui.get_first_node(options.$appendTo || options.$container);
10861
+ if (!append_node) {
10862
+ return null;
10863
+ }
10864
+
10865
+ const children = func.runtime.ui.get_children(append_node);
10866
+ for (let index = 0; index < children.length; index++) {
10867
+ const child = children[index];
10868
+ if (child?.__xuda_hydration_claimed) {
10869
+ continue;
10870
+ }
10871
+ if (func.runtime.ui.get_attr(child, 'data-xuda-tree-id') !== `${options.treeP.meta.tree_id}`) {
10872
+ continue;
10873
+ }
10874
+ child.__xuda_hydration_claimed = true;
10875
+ func.runtime.ui.set_attr(child, 'data-xuda-client-activation', 'hydrate');
10876
+ return child;
10877
+ }
10878
+
10879
+ return null;
10880
+ };
9907
10881
  func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
9908
10882
  const nodeId = nodeP.xu_tree_id || nodeP.id;
9909
10883
  const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
@@ -9956,7 +10930,11 @@ func.runtime.ui.create_container = async function (options) {
9956
10930
  try {
9957
10931
  const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
9958
10932
  const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
9959
- const $div = func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
10933
+ const hydration_candidate = func.runtime.ui.find_hydration_candidate({
10934
+ ...options,
10935
+ $appendTo,
10936
+ });
10937
+ const $div = hydration_candidate || func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
9960
10938
  const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
9961
10939
  container_xu_data,
9962
10940
  currentRecordId,
@@ -9982,9 +10960,10 @@ func.runtime.ui.create_container = async function (options) {
9982
10960
  parent_infoP: options.parent_infoP,
9983
10961
  is_placeholder: options.is_placeholder,
9984
10962
  classP: options.classP,
10963
+ treeP: options.treeP,
9985
10964
  });
9986
10965
 
9987
- if (options.div_typeP !== 'svg') {
10966
+ if (!hydration_candidate && options.div_typeP !== 'svg') {
9988
10967
  func.runtime.ui.append_to($div, $appendTo);
9989
10968
  }
9990
10969
  return $div;
@@ -12665,9 +13644,10 @@ func.runtime.ui.init_screen = async function (options) {
12665
13644
 
12666
13645
  const _session = SESSION_OBJ[SESSION_ID];
12667
13646
  const screenInfo = structuredClone(screen_ret);
13647
+ const ssr_payload = func.runtime.render.should_use_ssr_payload(SESSION_ID, { prog_id }) ? func.runtime.render.get_ssr_payload(_session) : null;
12668
13648
 
12669
13649
  const screen_type = source_functionP?.split('_')?.[1];
12670
- const screenId = (glb.screen_num++).toString();
13650
+ const screenId = ssr_payload?.screenId || (glb.screen_num++).toString();
12671
13651
 
12672
13652
  if (SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
12673
13653
  const wait_for_SCREEN_BLOCKER_release = function () {
@@ -12709,6 +13689,8 @@ func.runtime.ui.init_screen = async function (options) {
12709
13689
  call_screen_propertiesP,
12710
13690
  parentDataSourceNoP: _session.DS_GLB?.[callingDataSource_objP?.dsSession]?.dsSession || callingDataSource_objP?.parentDataSourceNo || 0,
12711
13691
  parameters_raw_obj,
13692
+ containerIdP: ssr_payload?.containerId || null,
13693
+ ssr_payload,
12712
13694
  };
12713
13695
 
12714
13696
  const screen_host = func.runtime.ui.create_screen_host(SESSION_ID, screen_type, params, $callingContainerP, screenId);
@@ -12741,6 +13723,10 @@ func.runtime.ui.init_screen = async function (options) {
12741
13723
  func.runtime.ui.set_style($rootFrame, 'display', 'contents');
12742
13724
  }
12743
13725
 
13726
+ if (screen_host.reused_ssr_host && func.runtime.render.is_takeover_mode(_session)) {
13727
+ func.runtime.ui.empty($rootFrame);
13728
+ }
13729
+
12744
13730
  if (!is_panelP) func.UI.utils.indicator.screen.busy();
12745
13731
 
12746
13732
  const ret = await func.datasource.create(
@@ -12782,7 +13768,24 @@ func.runtime.ui.init_screen = async function (options) {
12782
13768
  }
12783
13769
  let node = structuredClone(viewDoc.progUi);
12784
13770
  if (!node.length) return console.warn('ui node empty');
12785
- const ret_render_$container = await func.runtime.render.render_ui_tree(SESSION_ID, $rootFrame, node[0], null, params, jobNoP, null, null, null, null, null, $rootFrame);
13771
+ const root_tree = await func.runtime.render.build_tree({
13772
+ SESSION_ID,
13773
+ nodeP: node[0],
13774
+ paramsP: params,
13775
+ });
13776
+ const ret_render_$container = await func.runtime.render.render_tree(root_tree, {
13777
+ SESSION_ID,
13778
+ $container: $rootFrame,
13779
+ parent_infoP: null,
13780
+ paramsP: params,
13781
+ jobNoP,
13782
+ is_skeleton: null,
13783
+ keyP: null,
13784
+ refreshed_ds: null,
13785
+ parent_nodeP: null,
13786
+ check_existP: null,
13787
+ $root_container: $rootFrame,
13788
+ });
12786
13789
 
12787
13790
  if (!is_panelP) func.UI.utils.indicator.screen.normal();
12788
13791
 
@@ -13207,6 +14210,7 @@ func.runtime.ui.render_single_view_node = async function (options) {
13207
14210
  parent_infoP: options.parent_infoP,
13208
14211
  jobNoP: options.jobNoP,
13209
14212
  keyP: options.keyP,
14213
+ treeP: options.treeP,
13210
14214
  parent_nodeP: options.parent_nodeP,
13211
14215
  prop: options.prop,
13212
14216
  div_typeP: 'div',
@@ -13335,6 +14339,7 @@ func.runtime.ui.render_panel_node = async function (options) {
13335
14339
  parent_infoP: options.parent_infoP,
13336
14340
  jobNoP: options.jobNoP,
13337
14341
  keyP: options.keyP,
14342
+ treeP: options.treeP,
13338
14343
  parent_nodeP: options.parent_nodeP,
13339
14344
  prop: options.prop,
13340
14345
  $appendToP: $wrapper,
@@ -13664,6 +14669,14 @@ func.runtime.ui.screen_loading_done = async function (options) {
13664
14669
  });
13665
14670
 
13666
14671
  const _session = SESSION_OBJ[options.SESSION_ID];
14672
+ if (func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
14673
+ const root_node = func.runtime.ui.get_root_node(options.SESSION_ID);
14674
+ if (root_node) {
14675
+ func.runtime.ui.set_attr(root_node, 'data-xuda-client-activation', _session.opt.app_client_activation || 'none');
14676
+ func.runtime.ui.set_attr(root_node, 'data-xuda-ssr-status', _session.opt.app_client_activation === 'hydrate' ? 'hydrated' : 'taken-over');
14677
+ }
14678
+ func.runtime.render.mark_ssr_payload_consumed(options.SESSION_ID);
14679
+ }
13667
14680
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
13668
14681
  func.UI.utils.screen_blocker(false, options.paramsP.prog_id + (options.paramsP.sourceScreenP ? '_' + options.paramsP.sourceScreenP : ''));
13669
14682
  if (_session.prog_id === options.paramsP.prog_id) {
@@ -15506,6 +16519,9 @@ func.runtime.render.get_screen_context = function (SESSION_ID, $container, param
15506
16519
  };
15507
16520
  func.runtime.render.get_node_attributes = function (nodeP) {
15508
16521
  try {
16522
+ if (func.runtime.render.is_tree_node?.(nodeP)) {
16523
+ return nodeP.attributes;
16524
+ }
15509
16525
  return nodeP?.attributes;
15510
16526
  } catch (error) {
15511
16527
  return undefined;
@@ -15623,6 +16639,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15623
16639
  parent_infoP: options.parent_infoP,
15624
16640
  jobNoP: options.jobNoP,
15625
16641
  keyP: options.keyP,
16642
+ treeP: options.treeP,
15626
16643
  parent_nodeP: options.parent_nodeP,
15627
16644
  prop: options.prop,
15628
16645
  div_typeP: options.element,
@@ -15645,6 +16662,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15645
16662
  parent_infoP: options.parent_infoP,
15646
16663
  jobNoP: options.jobNoP,
15647
16664
  keyP: options.keyP,
16665
+ treeP: options.treeP,
15648
16666
  parent_nodeP: options.parent_nodeP,
15649
16667
  prop: options.prop,
15650
16668
  div_typeP: options.element,
@@ -16501,11 +17519,16 @@ func.runtime.widgets.render_node = async function (options) {
16501
17519
  parent_infoP: options.parent_infoP,
16502
17520
  jobNoP: options.jobNoP,
16503
17521
  keyP: options.keyP,
17522
+ treeP: options.treeP,
16504
17523
  parent_nodeP: options.parent_nodeP,
16505
17524
  prop: options.prop,
16506
17525
  classP: 'widget_wrapper',
16507
17526
  });
16508
17527
 
17528
+ if (func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID]) && func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
17529
+ func.runtime.ui.empty($div);
17530
+ }
17531
+
16509
17532
  const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, options.prop);
16510
17533
  const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
16511
17534
  const report_error = function (descP, warn) {
@@ -16608,9 +17631,306 @@ func.runtime.widgets = func.runtime.widgets || {};
16608
17631
 
16609
17632
  // Browser-only special node renderers live here so the core render tree can stay focused.
16610
17633
 
17634
+ const normalize_runtime_tag_name = function (tag_name) {
17635
+ return `${tag_name || ''}`.trim().toLowerCase();
17636
+ };
17637
+
17638
+ const get_runtime_node_attributes = function (nodeP) {
17639
+ if (!nodeP?.attributes || typeof nodeP.attributes !== 'object') {
17640
+ return {};
17641
+ }
17642
+
17643
+ return nodeP.attributes;
17644
+ };
17645
+
17646
+ const get_runtime_node_content = function (nodeP) {
17647
+ if (typeof nodeP?.content === 'string') {
17648
+ return nodeP.content;
17649
+ }
17650
+
17651
+ if (typeof nodeP?.text === 'string') {
17652
+ return nodeP.text;
17653
+ }
17654
+
17655
+ if (!Array.isArray(nodeP?.children)) {
17656
+ return '';
17657
+ }
17658
+
17659
+ return nodeP.children
17660
+ .map(function (child) {
17661
+ if (typeof child === 'string') {
17662
+ return child;
17663
+ }
17664
+ if (typeof child?.content === 'string') {
17665
+ return child.content;
17666
+ }
17667
+ if (typeof child?.text === 'string') {
17668
+ return child.text;
17669
+ }
17670
+ return '';
17671
+ })
17672
+ .join('');
17673
+ };
17674
+
17675
+ const get_runtime_asset_key = function (options, tag_name) {
17676
+ const source_node = options.treeP || options.nodeP || {};
17677
+ const parts = [
17678
+ 'xuda-html-asset',
17679
+ options.paramsP?.prog_id || '',
17680
+ tag_name,
17681
+ source_node.id || source_node.id_org || '',
17682
+ typeof options.keyP === 'undefined' || options.keyP === null ? '' : `${options.keyP}`,
17683
+ ].filter(function (part) {
17684
+ return `${part || ''}`.trim() !== '';
17685
+ });
17686
+
17687
+ return parts.join(':');
17688
+ };
17689
+
17690
+ const get_runtime_asset_signature = function (attributes, content) {
17691
+ const normalized_attributes = {};
17692
+ const attr_keys = Object.keys(attributes || {}).sort();
17693
+
17694
+ for (let index = 0; index < attr_keys.length; index++) {
17695
+ const key = attr_keys[index];
17696
+ normalized_attributes[key] = attributes[key];
17697
+ }
17698
+
17699
+ return JSON.stringify({
17700
+ attributes: normalized_attributes,
17701
+ content: `${content || ''}`,
17702
+ });
17703
+ };
17704
+
17705
+ const escape_runtime_asset_selector_value = function (value) {
17706
+ const win = func.runtime.platform.get_window?.();
17707
+ if (win?.CSS?.escape) {
17708
+ return win.CSS.escape(value);
17709
+ }
17710
+
17711
+ return `${value || ''}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
17712
+ };
17713
+
17714
+ const find_runtime_head_asset = function (head, tag_name, asset_key) {
17715
+ if (!head?.querySelector || !asset_key) {
17716
+ return null;
17717
+ }
17718
+
17719
+ return head.querySelector(`${tag_name}[data-xuda-asset-key="${escape_runtime_asset_selector_value(asset_key)}"]`);
17720
+ };
17721
+
17722
+ const find_runtime_head_asset_by_attr = function (head, tag_name, attr_name, attr_value) {
17723
+ if (!head?.querySelectorAll || !attr_name || !attr_value) {
17724
+ return null;
17725
+ }
17726
+
17727
+ const candidates = Array.from(head.querySelectorAll(tag_name));
17728
+ return (
17729
+ candidates.find(function (candidate) {
17730
+ return candidate.getAttribute(attr_name) === attr_value || candidate[attr_name] === attr_value;
17731
+ }) || null
17732
+ );
17733
+ };
17734
+
17735
+ const wait_for_runtime_asset_load = function (node) {
17736
+ if (!node?.addEventListener) {
17737
+ return Promise.resolve(node);
17738
+ }
17739
+
17740
+ if (node.getAttribute?.('data-xuda-loaded') === 'true') {
17741
+ return Promise.resolve(node);
17742
+ }
17743
+
17744
+ if (!node.getAttribute?.('data-xuda-asset-key')) {
17745
+ return Promise.resolve(node);
17746
+ }
17747
+
17748
+ const tag_name = normalize_runtime_tag_name(node.tagName);
17749
+ if (tag_name !== 'script' && !(tag_name === 'link' && normalize_runtime_tag_name(node.getAttribute?.('rel')) === 'stylesheet')) {
17750
+ return Promise.resolve(node);
17751
+ }
17752
+
17753
+ return new Promise(function (resolve) {
17754
+ const done = function () {
17755
+ node.setAttribute?.('data-xuda-loaded', 'true');
17756
+ resolve(node);
17757
+ };
17758
+
17759
+ node.addEventListener('load', done, { once: true });
17760
+ node.addEventListener('error', done, { once: true });
17761
+ });
17762
+ };
17763
+
17764
+ const remove_runtime_head_asset = function (node) {
17765
+ if (node?.parentNode?.removeChild) {
17766
+ node.parentNode.removeChild(node);
17767
+ }
17768
+ };
17769
+
17770
+ const apply_runtime_asset_metadata = function (node, asset_key, signature) {
17771
+ if (!node?.setAttribute) {
17772
+ return node;
17773
+ }
17774
+
17775
+ if (asset_key) {
17776
+ node.setAttribute('data-xuda-asset-key', asset_key);
17777
+ }
17778
+ node.setAttribute('data-xuda-asset-signature', signature);
17779
+ return node;
17780
+ };
17781
+
17782
+ const create_runtime_head_element = function (doc, tag_name, attributes, asset_key, signature) {
17783
+ const node = doc.createElement(tag_name);
17784
+ apply_runtime_asset_metadata(node, asset_key, signature);
17785
+ func.runtime.platform.apply_element_attributes(node, attributes);
17786
+ return node;
17787
+ };
17788
+
17789
+ const upsert_runtime_head_element = async function (options) {
17790
+ const doc = func.runtime.platform.get_document?.();
17791
+ const head = doc?.head;
17792
+ const tag_name = normalize_runtime_tag_name(options.tag_name);
17793
+
17794
+ if (!doc?.createElement || !head?.appendChild || !tag_name) {
17795
+ return null;
17796
+ }
17797
+
17798
+ const asset_key = options.asset_key || '';
17799
+ const signature = options.signature || '';
17800
+ const attributes = options.attributes || {};
17801
+ const content = typeof options.content === 'string' ? options.content : '';
17802
+
17803
+ const existing_by_key = find_runtime_head_asset(head, tag_name, asset_key);
17804
+ if (existing_by_key && existing_by_key.getAttribute('data-xuda-asset-signature') === signature) {
17805
+ return options.await_load ? await wait_for_runtime_asset_load(existing_by_key) : existing_by_key;
17806
+ }
17807
+
17808
+ if (existing_by_key) {
17809
+ remove_runtime_head_asset(existing_by_key);
17810
+ }
17811
+
17812
+ if (options.find_existing_attr?.name && options.find_existing_attr?.value) {
17813
+ const existing_by_attr = find_runtime_head_asset_by_attr(head, tag_name, options.find_existing_attr.name, options.find_existing_attr.value);
17814
+ if (existing_by_attr) {
17815
+ apply_runtime_asset_metadata(existing_by_attr, asset_key, signature);
17816
+ existing_by_attr.setAttribute?.('data-xuda-loaded', 'true');
17817
+ return existing_by_attr;
17818
+ }
17819
+ }
17820
+
17821
+ const node = create_runtime_head_element(doc, tag_name, attributes, asset_key, signature);
17822
+
17823
+ if (tag_name === 'script' && attributes.src && !Object.prototype.hasOwnProperty.call(attributes, 'async')) {
17824
+ const script_type = normalize_runtime_tag_name(attributes.type);
17825
+ if (script_type !== 'module') {
17826
+ node.async = false;
17827
+ }
17828
+ }
17829
+
17830
+ if (tag_name === 'style' || (tag_name === 'script' && !attributes.src)) {
17831
+ node.textContent = content;
17832
+ }
17833
+
17834
+ head.appendChild(node);
17835
+
17836
+ if (tag_name !== 'script' && tag_name !== 'link') {
17837
+ node.setAttribute('data-xuda-loaded', 'true');
17838
+ }
17839
+
17840
+ return options.await_load ? await wait_for_runtime_asset_load(node) : node;
17841
+ };
17842
+
17843
+ const render_runtime_html_asset = async function (options) {
17844
+ if (options.is_skeleton) {
17845
+ return options.$container;
17846
+ }
17847
+
17848
+ const nodeP = options.treeP || options.nodeP || {};
17849
+ const tag_name = normalize_runtime_tag_name(nodeP.tagName);
17850
+ const attributes = { ...get_runtime_node_attributes(nodeP) };
17851
+ const content = get_runtime_node_content(nodeP);
17852
+ const asset_key = get_runtime_asset_key(options, tag_name);
17853
+ const signature = get_runtime_asset_signature(attributes, content);
17854
+
17855
+ switch (tag_name) {
17856
+ case 'title':
17857
+ func.runtime.platform.set_title(content);
17858
+ return options.$container;
17859
+ case 'style':
17860
+ await upsert_runtime_head_element({
17861
+ tag_name,
17862
+ attributes,
17863
+ content,
17864
+ asset_key,
17865
+ signature,
17866
+ });
17867
+ return options.$container;
17868
+ case 'meta':
17869
+ if (!Object.keys(attributes).length) {
17870
+ return options.$container;
17871
+ }
17872
+ await upsert_runtime_head_element({
17873
+ tag_name,
17874
+ attributes,
17875
+ asset_key,
17876
+ signature,
17877
+ });
17878
+ return options.$container;
17879
+ case 'link': {
17880
+ const href = `${attributes.href || ''}`.trim();
17881
+ if (!href) {
17882
+ return options.$container;
17883
+ }
17884
+ await upsert_runtime_head_element({
17885
+ tag_name,
17886
+ attributes,
17887
+ asset_key,
17888
+ signature,
17889
+ find_existing_attr: {
17890
+ name: 'href',
17891
+ value: href,
17892
+ },
17893
+ await_load: normalize_runtime_tag_name(attributes.rel) === 'stylesheet',
17894
+ });
17895
+ return options.$container;
17896
+ }
17897
+ case 'script': {
17898
+ const src = `${attributes.src || ''}`.trim();
17899
+ if (!src && !content.trim()) {
17900
+ return options.$container;
17901
+ }
17902
+ await upsert_runtime_head_element({
17903
+ tag_name,
17904
+ attributes,
17905
+ content,
17906
+ asset_key,
17907
+ signature,
17908
+ find_existing_attr: src
17909
+ ? {
17910
+ name: 'src',
17911
+ value: src,
17912
+ }
17913
+ : null,
17914
+ await_load: !!src,
17915
+ });
17916
+ return options.$container;
17917
+ }
17918
+ default:
17919
+ return null;
17920
+ }
17921
+ };
17922
+
16611
17923
  func.runtime.render.render_special_node = async function (options) {
16612
- if (options.nodeP.content && options.nodeP.attributes) {
16613
- options.nodeP.attributes['xu-content'] = options.nodeP.content;
17924
+ const treeP = options.treeP || null;
17925
+ const nodeP = options.nodeP || func.runtime.render.get_tree_source_node(treeP);
17926
+ const render_tag_name = treeP?.tagName || nodeP?.tagName;
17927
+ const normalized_render_tag_name = normalize_runtime_tag_name(render_tag_name);
17928
+ const is_native_html_asset = ['title', 'style', 'meta', 'link', 'script'].includes(normalized_render_tag_name);
17929
+
17930
+ if (!is_native_html_asset && treeP?.content && nodeP?.attributes) {
17931
+ nodeP.attributes['xu-content'] = treeP.content;
17932
+ } else if (!is_native_html_asset && nodeP?.content && nodeP.attributes) {
17933
+ nodeP.attributes['xu-content'] = nodeP.content;
16614
17934
  }
16615
17935
 
16616
17936
  const renderers = {
@@ -16620,6 +17940,7 @@ func.runtime.render.render_special_node = async function (options) {
16620
17940
  SESSION_ID: options.SESSION_ID,
16621
17941
  $container: options.$container,
16622
17942
  $root_container: options.$root_container,
17943
+ treeP,
16623
17944
  nodeP: options.nodeP,
16624
17945
  parent_infoP: options.parent_infoP,
16625
17946
  paramsP: options.paramsP,
@@ -16636,6 +17957,7 @@ func.runtime.render.render_special_node = async function (options) {
16636
17957
  SESSION_ID: options.SESSION_ID,
16637
17958
  $container: options.$container,
16638
17959
  $root_container: options.$root_container,
17960
+ treeP,
16639
17961
  nodeP: options.nodeP,
16640
17962
  parent_infoP: options.parent_infoP,
16641
17963
  paramsP: options.paramsP,
@@ -16655,6 +17977,7 @@ func.runtime.render.render_special_node = async function (options) {
16655
17977
  SESSION_ID: options.SESSION_ID,
16656
17978
  $container: options.$container,
16657
17979
  $root_container: options.$root_container,
17980
+ treeP,
16658
17981
  nodeP: options.nodeP,
16659
17982
  parent_infoP: options.parent_infoP,
16660
17983
  paramsP: options.paramsP,
@@ -16671,6 +17994,7 @@ func.runtime.render.render_special_node = async function (options) {
16671
17994
  SESSION_ID: options.SESSION_ID,
16672
17995
  $container: options.$container,
16673
17996
  $root_container: options.$root_container,
17997
+ treeP,
16674
17998
  nodeP: options.nodeP,
16675
17999
  parent_infoP: options.parent_infoP,
16676
18000
  paramsP: options.paramsP,
@@ -16682,9 +18006,24 @@ func.runtime.render.render_special_node = async function (options) {
16682
18006
  refreshed_ds: options.refreshed_ds,
16683
18007
  });
16684
18008
  },
18009
+ title: async function () {
18010
+ return await render_runtime_html_asset(options);
18011
+ },
18012
+ style: async function () {
18013
+ return await render_runtime_html_asset(options);
18014
+ },
18015
+ meta: async function () {
18016
+ return await render_runtime_html_asset(options);
18017
+ },
18018
+ link: async function () {
18019
+ return await render_runtime_html_asset(options);
18020
+ },
18021
+ script: async function () {
18022
+ return await render_runtime_html_asset(options);
18023
+ },
16685
18024
  };
16686
18025
 
16687
- const renderer = renderers[options.nodeP.tagName];
18026
+ const renderer = renderers[normalized_render_tag_name];
16688
18027
  if (!renderer) {
16689
18028
  return { handled: false };
16690
18029
  }
@@ -16798,8 +18137,9 @@ func.runtime.widgets = func.runtime.widgets || {};
16798
18137
  // Browser-only render tree entrypoints live here so draw/cache helpers can stay focused.
16799
18138
 
16800
18139
  func.runtime.render.create_tree_runtime = function (options) {
18140
+ const render_node = options.nodeP || func.runtime.render.get_tree_source_node(options.treeP);
16801
18141
  const render_context = func.runtime.render.get_screen_context(options.SESSION_ID, options.$container, options.paramsP, options.is_skeleton);
16802
- const prop = func.runtime.render.get_node_attributes(options.nodeP);
18142
+ const prop = func.runtime.render.get_node_attributes(options.treeP || render_node);
16803
18143
  const is_mobile = render_context.is_mobile ? true : false;
16804
18144
  const hover_handlers = func.runtime.render.create_hover_handlers({
16805
18145
  SESSION_ID: options.SESSION_ID,
@@ -16814,13 +18154,22 @@ func.runtime.render.create_tree_runtime = function (options) {
16814
18154
  return await func.runtime.ui.close_modal_session(options.SESSION_ID, modal_id);
16815
18155
  };
16816
18156
  const iterate_child = async function ($divP, nodeP, parent_infoP, $root_container, before_record_function) {
18157
+ const child_tree = await func.runtime.render.ensure_tree_node({
18158
+ SESSION_ID: options.SESSION_ID,
18159
+ nodeP: nodeP || options.treeP || render_node,
18160
+ parent_infoP,
18161
+ paramsP: options.paramsP,
18162
+ keyP: options.keyP,
18163
+ parent_nodeP: render_node,
18164
+ pathP: options.treeP?.meta?.path || [],
18165
+ });
16817
18166
  return await func.runtime.render.iterate_children({
16818
18167
  $divP,
16819
- nodeP,
18168
+ nodeP: child_tree,
16820
18169
  is_mobile,
16821
18170
  before_record_function,
16822
18171
  render_child: async function (key, child) {
16823
- await options.render_child($divP, child, parent_infoP, key, nodeP, $root_container);
18172
+ await options.render_child($divP, child, parent_infoP, key, render_node, $root_container);
16824
18173
  },
16825
18174
  });
16826
18175
  };
@@ -16850,7 +18199,7 @@ func.runtime.render.draw_node = async function (options) {
16850
18199
  check_existP: options.check_existP,
16851
18200
  $root_container: options.$root_container,
16852
18201
  prop: options.prop,
16853
- element: options.nodeP.tagName,
18202
+ element: options.treeP?.tagName || options.nodeP.tagName,
16854
18203
  hover_handlers: options.hover_handlers,
16855
18204
  include_hover_click: options.include_hover_click,
16856
18205
  iterate_child: options.iterate_child,
@@ -16873,21 +18222,33 @@ func.runtime.render.draw_node = async function (options) {
16873
18222
  nodeP: options.nodeP,
16874
18223
  });
16875
18224
  };
16876
- func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container) {
16877
- if (!nodeP) return;
16878
- const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'render_ui_tree');
16879
- func.runtime?.perf?.increment_map?.(SESSION_ID, 'render_node_counts', nodeP.id || nodeP.id_org || nodeP.tagName || 'unknown');
18225
+ func.runtime.render.render_tree = async function (treeP, renderer_context) {
18226
+ if (!treeP) return;
18227
+ const nodeP = func.runtime.render.get_tree_source_node(treeP);
18228
+ const perf_end = func.runtime?.perf?.start?.(renderer_context.SESSION_ID, 'render_ui_tree');
18229
+ func.runtime?.perf?.increment_map?.(renderer_context.SESSION_ID, 'render_node_counts', treeP.id || nodeP?.id || nodeP?.id_org || treeP.tagName || 'unknown');
16880
18230
  try {
16881
18231
  const tree_runtime = func.runtime.render.create_tree_runtime({
16882
- SESSION_ID,
16883
- $container,
18232
+ SESSION_ID: renderer_context.SESSION_ID,
18233
+ $container: renderer_context.$container,
18234
+ treeP,
16884
18235
  nodeP,
16885
- parent_infoP,
16886
- paramsP,
16887
- jobNoP,
16888
- is_skeleton,
18236
+ parent_infoP: renderer_context.parent_infoP,
18237
+ paramsP: renderer_context.paramsP,
18238
+ jobNoP: renderer_context.jobNoP,
18239
+ is_skeleton: renderer_context.is_skeleton,
18240
+ keyP: renderer_context.keyP,
16889
18241
  render_child: async function ($divP, child, parent_infoP, key, parentNodeP, rootContainerP) {
16890
- await func.runtime.render.render_ui_tree(SESSION_ID, $divP, child, parent_infoP, paramsP, jobNoP, is_skeleton, key, null, parentNodeP, null, rootContainerP);
18242
+ await func.runtime.render.render_tree(child, {
18243
+ ...renderer_context,
18244
+ $container: $divP,
18245
+ parent_infoP,
18246
+ keyP: key,
18247
+ refreshed_ds: null,
18248
+ parent_nodeP: parentNodeP,
18249
+ check_existP: null,
18250
+ $root_container: rootContainerP,
18251
+ });
16891
18252
  },
16892
18253
  });
16893
18254
  const render_context = tree_runtime.render_context;
@@ -16899,23 +18260,24 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16899
18260
  const iterate_child = tree_runtime.iterate_child;
16900
18261
 
16901
18262
  func.runtime.render.log_tree_debug({
16902
- SESSION_ID,
16903
- paramsP,
18263
+ SESSION_ID: renderer_context.SESSION_ID,
18264
+ paramsP: renderer_context.paramsP,
16904
18265
  nodeP,
16905
18266
  _ds,
16906
18267
  });
16907
18268
  const special_render = await func.runtime.render.render_special_node({
16908
- SESSION_ID,
16909
- $container,
16910
- $root_container,
18269
+ SESSION_ID: renderer_context.SESSION_ID,
18270
+ $container: renderer_context.$container,
18271
+ $root_container: renderer_context.$root_container,
18272
+ treeP,
16911
18273
  nodeP,
16912
- parent_infoP,
16913
- paramsP,
16914
- jobNoP,
16915
- is_skeleton,
16916
- keyP,
16917
- refreshed_ds,
16918
- parent_nodeP,
18274
+ parent_infoP: renderer_context.parent_infoP,
18275
+ paramsP: renderer_context.paramsP,
18276
+ jobNoP: renderer_context.jobNoP,
18277
+ is_skeleton: renderer_context.is_skeleton,
18278
+ keyP: renderer_context.keyP,
18279
+ refreshed_ds: renderer_context.refreshed_ds,
18280
+ parent_nodeP: renderer_context.parent_nodeP,
16919
18281
  prop,
16920
18282
  render_context,
16921
18283
  hover_handlers,
@@ -16923,22 +18285,23 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16923
18285
  close_modal,
16924
18286
  });
16925
18287
  if (special_render.handled) {
16926
- func.runtime?.perf?.increment?.(SESSION_ID, 'render_special_node_hits');
18288
+ func.runtime?.perf?.increment?.(renderer_context.SESSION_ID, 'render_special_node_hits');
16927
18289
  return special_render.result;
16928
18290
  }
16929
18291
  return await func.runtime.render.draw_node({
16930
- SESSION_ID,
16931
- $container,
16932
- $root_container,
18292
+ SESSION_ID: renderer_context.SESSION_ID,
18293
+ $container: renderer_context.$container,
18294
+ $root_container: renderer_context.$root_container,
18295
+ treeP,
16933
18296
  nodeP,
16934
- parent_infoP,
16935
- paramsP,
16936
- jobNoP,
16937
- is_skeleton,
16938
- keyP,
16939
- refreshed_ds,
16940
- parent_nodeP,
16941
- check_existP,
18297
+ parent_infoP: renderer_context.parent_infoP,
18298
+ paramsP: renderer_context.paramsP,
18299
+ jobNoP: renderer_context.jobNoP,
18300
+ is_skeleton: renderer_context.is_skeleton,
18301
+ keyP: renderer_context.keyP,
18302
+ refreshed_ds: renderer_context.refreshed_ds,
18303
+ parent_nodeP: renderer_context.parent_nodeP,
18304
+ check_existP: renderer_context.check_existP,
16942
18305
  prop,
16943
18306
  hover_handlers,
16944
18307
  include_hover_click,
@@ -16947,6 +18310,31 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16947
18310
  } finally {
16948
18311
  perf_end?.();
16949
18312
  }
18313
+ };
18314
+ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container) {
18315
+ if (!nodeP) return;
18316
+ const treeP = await func.runtime.render.ensure_tree_node({
18317
+ SESSION_ID,
18318
+ nodeP,
18319
+ parent_infoP,
18320
+ paramsP,
18321
+ keyP,
18322
+ parent_nodeP,
18323
+ });
18324
+
18325
+ return await func.runtime.render.render_tree(treeP, {
18326
+ SESSION_ID,
18327
+ $container,
18328
+ parent_infoP,
18329
+ paramsP,
18330
+ jobNoP,
18331
+ is_skeleton,
18332
+ keyP,
18333
+ refreshed_ds,
18334
+ parent_nodeP,
18335
+ check_existP,
18336
+ $root_container,
18337
+ });
16950
18338
  };
16951
18339
  func.runtime = func.runtime || {};
16952
18340
  func.runtime.ui = func.runtime.ui || {};
@@ -18470,6 +19858,7 @@ func.runtime.render.handle_xu_panel_program = async function (options) {
18470
19858
  parent_infoP: options.parent_infoP,
18471
19859
  jobNoP: options.jobNoP,
18472
19860
  keyP: options.keyP,
19861
+ treeP: options.treeP,
18473
19862
  parent_nodeP: options.parent_nodeP,
18474
19863
  prop: options.nodeP.attributes,
18475
19864
  $appendToP: $wrapper,
@@ -22483,6 +23872,12 @@ function xuda(...args) {
22483
23872
  if (typeof opt !== 'object') {
22484
23873
  return console.error('Xuda Error - opt argument is not an object');
22485
23874
  }
23875
+
23876
+ if (!opt.ssr_payload && func.runtime.platform.get_window()?.__XUDA_SSR__) {
23877
+ opt.ssr_payload = func.runtime.platform.get_window().__XUDA_SSR__;
23878
+ }
23879
+ func.runtime.render.apply_runtime_bootstrap_defaults(opt);
23880
+
22486
23881
  glb.URL_PARAMS = func.common.getJsonFromUrl(platform.get_url_href());
22487
23882
 
22488
23883
  glb.worker_type = 'Worker';