@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.
@@ -23706,7 +23706,30 @@ func.runtime.platform.emit = function (name, data) {
23706
23706
  };
23707
23707
 
23708
23708
  // ── Platform helpers for DOM-independent resource loading ──
23709
- func.runtime.platform.load_script = function (url, type, callback) {
23709
+ func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
23710
+ if (!node?.setAttribute || !attributes) {
23711
+ return node;
23712
+ }
23713
+
23714
+ const excluded = new Set(excluded_keys || []);
23715
+ const attr_keys = Object.keys(attributes);
23716
+ for (let index = 0; index < attr_keys.length; index++) {
23717
+ const key = attr_keys[index];
23718
+ if (!key || excluded.has(key)) {
23719
+ continue;
23720
+ }
23721
+
23722
+ const value = attributes[key];
23723
+ if (value === false || typeof value === 'undefined') {
23724
+ continue;
23725
+ }
23726
+
23727
+ node.setAttribute(key, value === null ? '' : `${value}`);
23728
+ }
23729
+
23730
+ return node;
23731
+ };
23732
+ func.runtime.platform.load_script = function (url, type, callback, attributes) {
23710
23733
  const doc = func.runtime.platform.get_document();
23711
23734
  if (!doc?.createElement || !doc?.head?.appendChild) {
23712
23735
  if (callback) {
@@ -23714,19 +23737,63 @@ func.runtime.platform.load_script = function (url, type, callback) {
23714
23737
  }
23715
23738
  return;
23716
23739
  }
23740
+ const find_existing_script = function () {
23741
+ const asset_key = attributes?.['data-xuda-asset-key'];
23742
+ const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
23743
+ return scripts.find(function (script) {
23744
+ if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
23745
+ return true;
23746
+ }
23747
+ return !!(url && script.getAttribute('src') === url);
23748
+ }) || null;
23749
+ };
23750
+
23751
+ const existing_script = find_existing_script();
23752
+ if (existing_script) {
23753
+ if (callback) {
23754
+ if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
23755
+ callback();
23756
+ } else {
23757
+ existing_script.addEventListener('load', callback, { once: true });
23758
+ existing_script.addEventListener('error', callback, { once: true });
23759
+ }
23760
+ }
23761
+ return existing_script;
23762
+ }
23763
+
23717
23764
  const script = doc.createElement('script');
23718
23765
  script.src = url;
23719
23766
  if (type) script.type = type;
23720
- script.onload = callback;
23767
+ func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
23768
+ script.onload = function () {
23769
+ script.setAttribute('data-xuda-loaded', 'true');
23770
+ if (callback) {
23771
+ callback();
23772
+ }
23773
+ };
23774
+ script.onerror = function () {
23775
+ if (callback) {
23776
+ callback();
23777
+ }
23778
+ };
23721
23779
  doc.head.appendChild(script);
23780
+ return script;
23722
23781
  };
23723
- func.runtime.platform.load_css = function (href) {
23782
+ func.runtime.platform.load_css = function (href, attributes) {
23724
23783
  const doc = func.runtime.platform.get_document();
23725
23784
  if (!doc?.createElement || !doc?.head) {
23726
23785
  return;
23727
23786
  }
23728
23787
  try {
23729
- if (doc.querySelector('link[href="' + href + '"]')) return;
23788
+ const asset_key = attributes?.['data-xuda-asset-key'];
23789
+ const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
23790
+ const existing = existing_links.find(function (link) {
23791
+ if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
23792
+ return true;
23793
+ }
23794
+ return !!(href && link.getAttribute('href') === href);
23795
+ });
23796
+ if (existing) return existing;
23730
23797
  } catch (err) {
23731
23798
  return;
23732
23799
  }
@@ -23734,7 +23801,9 @@ func.runtime.platform.load_css = function (href) {
23734
23801
  link.rel = 'stylesheet';
23735
23802
  link.type = 'text/css';
23736
23803
  link.href = href;
23804
+ func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
23737
23805
  doc.head.insertBefore(link, doc.head.firstChild);
23806
+ return link;
23738
23807
  };
23739
23808
  func.runtime.platform.remove_js_css = function (filename, filetype) {
23740
23809
  const doc = func.runtime.platform.get_document();
@@ -24025,6 +24094,147 @@ func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_q
24025
24094
  delete registry_entry.promise_queue[promise_queue_id];
24026
24095
  return true;
24027
24096
  };
24097
+ func.runtime.render.clone_runtime_options = function (value) {
24098
+ if (typeof structuredClone === 'function') {
24099
+ try {
24100
+ return structuredClone(value);
24101
+ } catch (_) {}
24102
+ }
24103
+
24104
+ if (Array.isArray(value)) {
24105
+ return value.map(function (item) {
24106
+ return func.runtime.render.clone_runtime_options(item);
24107
+ });
24108
+ }
24109
+
24110
+ if (value && typeof value === 'object') {
24111
+ const cloned = {};
24112
+ const keys = Object.keys(value);
24113
+ for (let index = 0; index < keys.length; index++) {
24114
+ const key = keys[index];
24115
+ cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
24116
+ }
24117
+ return cloned;
24118
+ }
24119
+
24120
+ return value;
24121
+ };
24122
+ func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
24123
+ const options = raw_options || {};
24124
+ let app_computing_mode = options.app_computing_mode || '';
24125
+ let app_render_mode = options.app_render_mode || '';
24126
+ let app_client_activation = options.app_client_activation || '';
24127
+ let ssr_payload = options.ssr_payload || null;
24128
+
24129
+ if (typeof ssr_payload === 'string') {
24130
+ try {
24131
+ ssr_payload = JSON.parse(ssr_payload);
24132
+ } catch (_) {
24133
+ ssr_payload = null;
24134
+ }
24135
+ }
24136
+
24137
+ if (ssr_payload && typeof ssr_payload === 'object') {
24138
+ ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
24139
+ }
24140
+
24141
+ if (!app_computing_mode) {
24142
+ if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
24143
+ app_computing_mode = 'server';
24144
+ } else {
24145
+ app_computing_mode = 'main';
24146
+ }
24147
+ }
24148
+
24149
+ switch (app_computing_mode) {
24150
+ case 'main':
24151
+ app_render_mode = 'csr';
24152
+ app_client_activation = 'none';
24153
+ break;
24154
+
24155
+ case 'worker':
24156
+ app_render_mode = 'csr';
24157
+ app_client_activation = 'none';
24158
+ break;
24159
+
24160
+ default:
24161
+ app_computing_mode = 'server';
24162
+ if (app_render_mode !== 'ssr_full') {
24163
+ app_render_mode = 'ssr_first_page';
24164
+ }
24165
+ app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
24166
+ break;
24167
+ }
24168
+
24169
+ if (ssr_payload && typeof ssr_payload === 'object') {
24170
+ if (!ssr_payload.app_render_mode) {
24171
+ ssr_payload.app_render_mode = app_render_mode;
24172
+ }
24173
+ if (!ssr_payload.app_client_activation) {
24174
+ ssr_payload.app_client_activation = app_client_activation;
24175
+ }
24176
+ if (!ssr_payload.app_computing_mode) {
24177
+ ssr_payload.app_computing_mode = app_computing_mode;
24178
+ }
24179
+ }
24180
+
24181
+ return {
24182
+ app_computing_mode,
24183
+ app_render_mode,
24184
+ app_client_activation,
24185
+ ssr_payload,
24186
+ };
24187
+ };
24188
+ func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
24189
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
24190
+ target.app_computing_mode = normalized.app_computing_mode;
24191
+ target.app_render_mode = normalized.app_render_mode;
24192
+ target.app_client_activation = normalized.app_client_activation;
24193
+ target.ssr_payload = normalized.ssr_payload;
24194
+ return normalized;
24195
+ };
24196
+ func.runtime.render.is_server_render_mode = function (target = {}) {
24197
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
24198
+ return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
24199
+ };
24200
+ func.runtime.render.is_takeover_mode = function (target = {}) {
24201
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
24202
+ return normalized.app_client_activation === 'takeover';
24203
+ };
24204
+ func.runtime.render.is_hydration_mode = function (target = {}) {
24205
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
24206
+ return normalized.app_client_activation === 'hydrate';
24207
+ };
24208
+ func.runtime.render.get_ssr_payload = function (target = {}) {
24209
+ if (target?.opt?.ssr_payload) {
24210
+ return target.opt.ssr_payload;
24211
+ }
24212
+ if (target?.ssr_payload) {
24213
+ return target.ssr_payload;
24214
+ }
24215
+ const win = func.runtime.platform.get_window();
24216
+ return win?.__XUDA_SSR__ || null;
24217
+ };
24218
+ func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
24219
+ const session = SESSION_OBJ?.[SESSION_ID];
24220
+ const payload = func.runtime.render.get_ssr_payload(session);
24221
+ if (!payload || payload._consumed) {
24222
+ return false;
24223
+ }
24224
+ if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
24225
+ return false;
24226
+ }
24227
+ return true;
24228
+ };
24229
+ func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
24230
+ const session = SESSION_OBJ?.[SESSION_ID];
24231
+ const payload = func.runtime.render.get_ssr_payload(session);
24232
+ if (!payload || typeof payload !== 'object') {
24233
+ return false;
24234
+ }
24235
+ payload._consumed = true;
24236
+ return true;
24237
+ };
24028
24238
  func.runtime.render.get_root_data_system = function (SESSION_ID) {
24029
24239
  return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
24030
24240
  };
@@ -24428,10 +24638,10 @@ func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
24428
24638
  await func.utils.load_js_on_demand(normalized_resource.src);
24429
24639
  break;
24430
24640
  case 'css':
24431
- await func.utils.load_js_on_demand(normalized_resource.src);
24641
+ func.runtime.platform.load_css(normalized_resource.src);
24432
24642
  break;
24433
24643
  case 'module':
24434
- func.utils.load_js_on_demand(normalized_resource.src, 'module');
24644
+ await func.utils.load_js_on_demand(normalized_resource.src, 'module');
24435
24645
  break;
24436
24646
  default:
24437
24647
  await func.utils.load_js_on_demand(normalized_resource.src);
@@ -25744,30 +25954,715 @@ func.common.get_data_from_websocket = async function (SESSION_ID, serviceP, data
25744
25954
  // // The .toString(36) method handles the conversion to an alphanumeric representation (0-9, a-z).
25745
25955
  // const base36Hash = bigInt.toString(36);
25746
25956
 
25747
- // // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
25748
- // // For a 64-bit integer, the Base36 representation will be about 13 characters long,
25749
- // // so slicing is a reliable way to get a fixed length.
25750
- // const shortHash = base36Hash.slice(0, 10);
25957
+ // // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
25958
+ // // For a 64-bit integer, the Base36 representation will be about 13 characters long,
25959
+ // // so slicing is a reliable way to get a fixed length.
25960
+ // const shortHash = base36Hash.slice(0, 10);
25961
+
25962
+ // // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
25963
+ // // This ensures the output is always exactly 10 characters long.
25964
+ // return shortHash.padStart(10, '0');
25965
+ // };
25966
+
25967
+ func.common.fastHash = function (inputString) {
25968
+ let hash = 0x811c9dc5; // FNV offset basis
25969
+
25970
+ for (let i = 0; i < inputString.length; i++) {
25971
+ hash ^= inputString.charCodeAt(i);
25972
+ // FNV prime multiplication with 32-bit overflow
25973
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
25974
+ }
25975
+
25976
+ // Convert to base36 and pad to 10 characters
25977
+ return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
25978
+ };
25979
+
25980
+ glb.new_xu_render = false;
25981
+ func.runtime = func.runtime || {};
25982
+ func.runtime.ui = func.runtime.ui || {};
25983
+ func.runtime.render = func.runtime.render || {};
25984
+ func.runtime.widgets = func.runtime.widgets || {};
25985
+
25986
+ // Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
25987
+
25988
+ func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
25989
+ func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
25990
+
25991
+ func.runtime.render.safe_clone_tree_value = function (value) {
25992
+ if (typeof structuredClone === 'function') {
25993
+ try {
25994
+ return structuredClone(value);
25995
+ } catch (_) {
25996
+ // Fall through to the recursive clone below.
25997
+ }
25998
+ }
25999
+
26000
+ if (Array.isArray(value)) {
26001
+ return value.map(function (item) {
26002
+ return func.runtime.render.safe_clone_tree_value(item);
26003
+ });
26004
+ }
26005
+
26006
+ if (value && typeof value === 'object') {
26007
+ const cloned = {};
26008
+ const keys = Object.keys(value);
26009
+ for (let index = 0; index < keys.length; index++) {
26010
+ const key = keys[index];
26011
+ cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
26012
+ }
26013
+ return cloned;
26014
+ }
26015
+
26016
+ return value;
26017
+ };
26018
+ func.runtime.render.sort_tree_debug_value = function (value) {
26019
+ if (Array.isArray(value)) {
26020
+ return value.map(function (item) {
26021
+ return func.runtime.render.sort_tree_debug_value(item);
26022
+ });
26023
+ }
26024
+
26025
+ if (value && typeof value === 'object') {
26026
+ const sorted = {};
26027
+ const keys = Object.keys(value).sort();
26028
+ for (let index = 0; index < keys.length; index++) {
26029
+ const key = keys[index];
26030
+ sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
26031
+ }
26032
+ return sorted;
26033
+ }
26034
+
26035
+ return value;
26036
+ };
26037
+ func.runtime.render.is_tree_node = function (nodeP) {
26038
+ return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
26039
+ };
26040
+ func.runtime.render.get_tree_source_node = function (nodeP) {
26041
+ if (!func.runtime.render.is_tree_node(nodeP)) {
26042
+ return nodeP || null;
26043
+ }
26044
+ return nodeP?.meta?.source_node || null;
26045
+ };
26046
+ func.runtime.render.get_tree_source_snapshot = function (nodeP) {
26047
+ if (!func.runtime.render.is_tree_node(nodeP)) {
26048
+ return func.runtime.render.safe_clone_tree_value(nodeP);
26049
+ }
26050
+ return nodeP?.meta?.source_snapshot || null;
26051
+ };
26052
+ func.runtime.render.get_tree_node_kind = function (nodeP) {
26053
+ const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
26054
+ const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
26055
+
26056
+ if (tag_name === 'xu-widget') return 'widget';
26057
+ if (tag_name === 'xu-single-view') return 'single_view';
26058
+ if (tag_name === 'xu-multi-view') return 'multi_view';
26059
+ if (tag_name === 'xu-panel') return 'panel';
26060
+ if (tag_name === 'xu-teleport') return 'teleport';
26061
+ if (tag_name === 'xurender') return 'placeholder';
26062
+ if (tag_name === '#text' || node_type === 'text') return 'text';
26063
+ if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
26064
+ return 'element';
26065
+ };
26066
+ func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
26067
+ if (nodeP?.id) {
26068
+ return nodeP.id;
26069
+ }
26070
+ if (nodeP?.id_org) {
26071
+ return nodeP.id_org;
26072
+ }
26073
+ const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
26074
+ return `tree-node-${normalized_path}`;
26075
+ };
26076
+ func.runtime.render.get_tree_controls = function (attributes) {
26077
+ const attrs = attributes || {};
26078
+ const get_first_defined = function (keys) {
26079
+ for (let index = 0; index < keys.length; index++) {
26080
+ const key = keys[index];
26081
+ if (Object.prototype.hasOwnProperty.call(attrs, key)) {
26082
+ return attrs[key];
26083
+ }
26084
+ }
26085
+ return null;
26086
+ };
26087
+ return {
26088
+ xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
26089
+ xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
26090
+ xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
26091
+ };
26092
+ };
26093
+ func.runtime.render.get_tree_node_capabilities = async function (options) {
26094
+ const attributes = options?.attributes || {};
26095
+ const plugin_name = attributes['xu-widget'];
26096
+ if (!plugin_name) {
26097
+ return null;
26098
+ }
26099
+
26100
+ const cache = func.runtime.render._tree_widget_capability_cache;
26101
+ if (cache[plugin_name]) {
26102
+ return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
26103
+ }
26104
+
26105
+ let capabilities = {
26106
+ browser: true,
26107
+ headless: false,
26108
+ };
26109
+
26110
+ try {
26111
+ if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
26112
+ const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
26113
+ const definition = await func.runtime.widgets.get_definition(widget_context);
26114
+ capabilities = func.runtime.widgets.normalize_capabilities(definition);
26115
+ }
26116
+ } catch (_) {
26117
+ // Keep the safe browser-only default when the widget definition is unavailable.
26118
+ }
26119
+
26120
+ cache[plugin_name] = capabilities;
26121
+ return func.runtime.render.safe_clone_tree_value(capabilities);
26122
+ };
26123
+ func.runtime.render.ensure_tree_node = async function (options) {
26124
+ if (!options?.nodeP) {
26125
+ return null;
26126
+ }
26127
+ if (func.runtime.render.is_tree_node(options.nodeP)) {
26128
+ return options.nodeP;
26129
+ }
26130
+ return await func.runtime.render.build_tree(options);
26131
+ };
26132
+ func.runtime.render.build_tree = async function (options) {
26133
+ if (Array.isArray(options?.nodeP)) {
26134
+ return await func.runtime.render.build_tree_list({
26135
+ ...options,
26136
+ nodesP: options.nodeP,
26137
+ });
26138
+ }
26139
+
26140
+ const nodeP = options?.nodeP;
26141
+ if (!nodeP) {
26142
+ return null;
26143
+ }
26144
+ if (func.runtime.render.is_tree_node(nodeP)) {
26145
+ return nodeP;
26146
+ }
26147
+
26148
+ const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
26149
+ const tree_path = pathP.length ? pathP.slice() : [0];
26150
+ const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
26151
+ if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined') {
26152
+ attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
26153
+ }
26154
+
26155
+ const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
26156
+ SESSION_ID: options?.SESSION_ID,
26157
+ paramsP: options?.paramsP,
26158
+ attributes,
26159
+ });
26160
+ const children = [];
26161
+ const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
26162
+ const parent_tree_id = tree_path.join('.');
26163
+
26164
+ for (let index = 0; index < child_nodes.length; index++) {
26165
+ const child_tree = await func.runtime.render.build_tree({
26166
+ ...options,
26167
+ nodeP: child_nodes[index],
26168
+ pathP: tree_path.concat(index),
26169
+ parent_tree_id: parent_tree_id,
26170
+ keyP: index,
26171
+ parent_nodeP: nodeP,
26172
+ });
26173
+ if (child_tree) {
26174
+ children.push(child_tree);
26175
+ }
26176
+ }
26177
+
26178
+ const tree = {
26179
+ contract: func.runtime.render.TREE_CONTRACT_VERSION,
26180
+ id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
26181
+ xu_tree_id: `tree.${tree_path.join('.')}`,
26182
+ kind: func.runtime.render.get_tree_node_kind(nodeP),
26183
+ tagName: nodeP.tagName || null,
26184
+ attributes,
26185
+ text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
26186
+ content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
26187
+ children,
26188
+ meta: {
26189
+ tree_id: tree_path.join('.'),
26190
+ path: tree_path,
26191
+ parent_tree_id: options?.parent_tree_id || null,
26192
+ key: typeof options?.keyP === 'undefined' ? null : options.keyP,
26193
+ recordid: nodeP?.recordid || null,
26194
+ dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
26195
+ iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
26196
+ controls: func.runtime.render.get_tree_controls(attributes),
26197
+ capabilities: widget_capabilities,
26198
+ widget: attributes['xu-widget']
26199
+ ? {
26200
+ plugin_name: attributes['xu-widget'],
26201
+ method: attributes['xu-method'] || '_default',
26202
+ capabilities: widget_capabilities,
26203
+ }
26204
+ : null,
26205
+ source_node_id: nodeP?.id || nodeP?.id_org || null,
26206
+ source_node: nodeP,
26207
+ source_snapshot: func.runtime.ui?.get_node_snapshot
26208
+ ? func.runtime.ui.get_node_snapshot(nodeP)
26209
+ : func.runtime.render.safe_clone_tree_value(nodeP),
26210
+ },
26211
+ };
26212
+
26213
+ return tree;
26214
+ };
26215
+ func.runtime.render.build_tree_list = async function (options) {
26216
+ const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
26217
+ const trees = [];
26218
+
26219
+ for (let index = 0; index < nodes.length; index++) {
26220
+ const tree = await func.runtime.render.build_tree({
26221
+ ...options,
26222
+ nodeP: nodes[index],
26223
+ pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
26224
+ keyP: index,
26225
+ });
26226
+ if (tree) {
26227
+ trees.push(tree);
26228
+ }
26229
+ }
26230
+
26231
+ return trees;
26232
+ };
26233
+ func.runtime.render.sanitize_tree_for_debug = function (treeP) {
26234
+ if (Array.isArray(treeP)) {
26235
+ return treeP.map(function (child) {
26236
+ return func.runtime.render.sanitize_tree_for_debug(child);
26237
+ });
26238
+ }
26239
+
26240
+ if (!func.runtime.render.is_tree_node(treeP)) {
26241
+ return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
26242
+ }
26243
+
26244
+ return {
26245
+ contract: treeP.contract,
26246
+ id: treeP.id,
26247
+ xu_tree_id: treeP.xu_tree_id || null,
26248
+ kind: treeP.kind,
26249
+ tagName: treeP.tagName,
26250
+ attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
26251
+ text: treeP.text,
26252
+ content: treeP.content,
26253
+ children: treeP.children.map(function (child) {
26254
+ return func.runtime.render.sanitize_tree_for_debug(child);
26255
+ }),
26256
+ meta: {
26257
+ tree_id: treeP.meta?.tree_id || null,
26258
+ path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
26259
+ parent_tree_id: treeP.meta?.parent_tree_id || null,
26260
+ key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
26261
+ recordid: treeP.meta?.recordid || null,
26262
+ dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
26263
+ iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
26264
+ controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
26265
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
26266
+ widget: treeP.meta?.widget
26267
+ ? {
26268
+ plugin_name: treeP.meta.widget.plugin_name,
26269
+ method: treeP.meta.widget.method,
26270
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
26271
+ }
26272
+ : null,
26273
+ source_node_id: treeP.meta?.source_node_id || null,
26274
+ },
26275
+ };
26276
+ };
26277
+ func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
26278
+ return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
26279
+ };
26280
+ func.runtime = func.runtime || {};
26281
+ func.runtime.ui = func.runtime.ui || {};
26282
+ func.runtime.render = func.runtime.render || {};
26283
+ func.runtime.widgets = func.runtime.widgets || {};
26284
+
26285
+ // Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
26286
+
26287
+ func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
26288
+ area: true,
26289
+ base: true,
26290
+ br: true,
26291
+ col: true,
26292
+ embed: true,
26293
+ hr: true,
26294
+ img: true,
26295
+ input: true,
26296
+ link: true,
26297
+ meta: true,
26298
+ param: true,
26299
+ source: true,
26300
+ track: true,
26301
+ wbr: true,
26302
+ };
26303
+ func.runtime.render.escape_html = function (value) {
26304
+ return `${value ?? ''}`
26305
+ .replaceAll('&', '&amp;')
26306
+ .replaceAll('<', '&lt;')
26307
+ .replaceAll('>', '&gt;')
26308
+ .replaceAll('"', '&quot;')
26309
+ .replaceAll("'", '&#039;');
26310
+ };
26311
+ func.runtime.render.escape_html_attribute = function (value) {
26312
+ return func.runtime.render.escape_html(value);
26313
+ };
26314
+ func.runtime.render.is_html_void_tag = function (tag_name) {
26315
+ return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
26316
+ };
26317
+ func.runtime.render.is_falsey_render_value = function (value) {
26318
+ if (value === false || value === null || typeof value === 'undefined') {
26319
+ return true;
26320
+ }
26321
+ if (typeof value === 'number') {
26322
+ return value === 0;
26323
+ }
26324
+ if (typeof value === 'string') {
26325
+ const normalized = value.trim().toLowerCase();
26326
+ return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
26327
+ }
26328
+ return false;
26329
+ };
26330
+ func.runtime.render.should_render_tree_node = function (treeP) {
26331
+ const controls = treeP?.meta?.controls || {};
26332
+ if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
26333
+ return false;
26334
+ }
26335
+ if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
26336
+ return false;
26337
+ }
26338
+ return true;
26339
+ };
26340
+ func.runtime.render.is_tree_control_attribute = function (key) {
26341
+ if (!key) {
26342
+ return false;
26343
+ }
26344
+ return (
26345
+ key.startsWith('xu-exp:') ||
26346
+ key === 'xu-widget' ||
26347
+ key === 'xu-method' ||
26348
+ key === 'xu-for' ||
26349
+ key === 'xu-for-key' ||
26350
+ key === 'xu-for-val' ||
26351
+ key === 'xu-if' ||
26352
+ key === 'xu-render' ||
26353
+ key === 'xu-bind' ||
26354
+ key === 'xu-content' ||
26355
+ key === 'xu-text' ||
26356
+ key === 'xu-html' ||
26357
+ key === 'xu-show' ||
26358
+ key === 'xu-panel-program' ||
26359
+ key === 'xu-teleport'
26360
+ );
26361
+ };
26362
+ func.runtime.render.get_string_renderer_tag_name = function (treeP) {
26363
+ switch (treeP?.kind) {
26364
+ case 'widget':
26365
+ case 'single_view':
26366
+ case 'multi_view':
26367
+ case 'panel':
26368
+ case 'teleport':
26369
+ return 'div';
26370
+ case 'placeholder':
26371
+ return null;
26372
+ case 'text':
26373
+ return null;
26374
+ default:
26375
+ return treeP?.tagName || 'div';
26376
+ }
26377
+ };
26378
+ func.runtime.render.get_tree_terminal_content = function (treeP) {
26379
+ const attributes = treeP?.attributes || {};
26380
+ if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
26381
+ return {
26382
+ value: `${attributes['xu-html']}`,
26383
+ mode: 'html',
26384
+ };
26385
+ }
26386
+ if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
26387
+ return {
26388
+ value: `${attributes['xu-content']}`,
26389
+ mode: 'html',
26390
+ };
26391
+ }
26392
+ if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
26393
+ return {
26394
+ value: `${attributes['xu-text']}`,
26395
+ mode: 'text',
26396
+ };
26397
+ }
26398
+ if (treeP?.kind === 'text') {
26399
+ return {
26400
+ value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
26401
+ mode: 'text',
26402
+ };
26403
+ }
26404
+ return null;
26405
+ };
26406
+ func.runtime.render.render_tree_terminal_content = function (treeP) {
26407
+ const terminal = func.runtime.render.get_tree_terminal_content(treeP);
26408
+ if (!terminal) {
26409
+ return null;
26410
+ }
26411
+ if (terminal.mode === 'html') {
26412
+ return terminal.value;
26413
+ }
26414
+ return func.runtime.render.escape_html(terminal.value);
26415
+ };
26416
+ func.runtime.render.get_widget_fallback_markup = function (treeP) {
26417
+ const widget_meta = treeP?.meta?.widget || {};
26418
+ const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
26419
+ return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
26420
+ };
26421
+ func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
26422
+ const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
26423
+ const attr_pairs = [];
26424
+ const keys = Object.keys(attributes);
26425
+
26426
+ for (let index = 0; index < keys.length; index++) {
26427
+ const key = keys[index];
26428
+ if (func.runtime.render.is_tree_control_attribute(key)) {
26429
+ continue;
26430
+ }
26431
+ const value = attributes[key];
26432
+ if (value === false || value === null || typeof value === 'undefined') {
26433
+ continue;
26434
+ }
26435
+ if (value === true) {
26436
+ attr_pairs.push(key);
26437
+ continue;
26438
+ }
26439
+ const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
26440
+ attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
26441
+ }
26442
+
26443
+ attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
26444
+ attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
26445
+ attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
26446
+
26447
+ if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
26448
+ attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
26449
+ attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
26450
+ attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
26451
+ }
26452
+
26453
+ if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
26454
+ attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
26455
+ }
26456
+
26457
+ if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
26458
+ attr_pairs.push('data-xuda-xu-for="pending"');
26459
+ }
26460
+
26461
+ return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
26462
+ };
26463
+ func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
26464
+ if (!Array.isArray(treeP?.children) || !treeP.children.length) {
26465
+ return '';
26466
+ }
26467
+ let html = '';
26468
+ for (let index = 0; index < treeP.children.length; index++) {
26469
+ html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
26470
+ ...renderer_context,
26471
+ parent_tree: treeP,
26472
+ });
26473
+ }
26474
+ return html;
26475
+ };
26476
+ func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
26477
+ if (!treeP) {
26478
+ return '';
26479
+ }
26480
+ if (Array.isArray(treeP)) {
26481
+ let html = '';
26482
+ for (let index = 0; index < treeP.length; index++) {
26483
+ html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
26484
+ }
26485
+ return html;
26486
+ }
26487
+
26488
+ const ensured_tree = await func.runtime.render.ensure_tree_node({
26489
+ SESSION_ID: renderer_context?.SESSION_ID,
26490
+ nodeP: treeP,
26491
+ paramsP: renderer_context?.paramsP,
26492
+ parent_infoP: renderer_context?.parent_infoP,
26493
+ keyP: renderer_context?.keyP,
26494
+ parent_nodeP: renderer_context?.parent_nodeP,
26495
+ });
26496
+
26497
+ if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
26498
+ return '';
26499
+ }
26500
+
26501
+ if (ensured_tree.kind === 'placeholder') {
26502
+ if (renderer_context?.include_placeholders) {
26503
+ return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
26504
+ }
26505
+ return '';
26506
+ }
26507
+
26508
+ if (ensured_tree.kind === 'text') {
26509
+ return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
26510
+ }
26511
+
26512
+ const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
26513
+ if (!tag_name || tag_name.toLowerCase() === 'script') {
26514
+ return '';
26515
+ }
26516
+
26517
+ const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
26518
+ const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
26519
+ let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
26520
+
26521
+ if (ensured_tree.kind === 'widget' && !children_html) {
26522
+ children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
26523
+ }
26524
+
26525
+ if (func.runtime.render.is_html_void_tag(tag_name)) {
26526
+ return `<${tag_name}${attributes}>`;
26527
+ }
26528
+
26529
+ return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
26530
+ };
26531
+ func.runtime.render.render_to_string = async function (options = {}) {
26532
+ const treeP = await func.runtime.render.ensure_tree_node({
26533
+ SESSION_ID: options.SESSION_ID,
26534
+ nodeP: options.treeP || options.nodeP,
26535
+ paramsP: options.paramsP,
26536
+ parent_infoP: options.parent_infoP,
26537
+ keyP: options.keyP,
26538
+ parent_nodeP: options.parent_nodeP,
26539
+ });
26540
+
26541
+ return await func.runtime.render.render_tree_to_string(treeP, options);
26542
+ };
26543
+ func.runtime.render.get_server_render_mode = function (options = {}) {
26544
+ const normalized = func.runtime.render.normalize_runtime_bootstrap({
26545
+ app_computing_mode: options.app_computing_mode,
26546
+ app_render_mode: options.app_render_mode,
26547
+ app_client_activation: options.app_client_activation,
26548
+ });
26549
+
26550
+ return normalized;
26551
+ };
26552
+ func.runtime.render.build_server_render_params = async function (options = {}) {
26553
+ const SESSION_ID = options.SESSION_ID;
26554
+ const prog_id = options.prog_id;
26555
+ const dsSessionP = options.dsSessionP;
26556
+ const _session = SESSION_OBJ?.[SESSION_ID] || {};
26557
+ const _ds = _session?.DS_GLB?.[dsSessionP] || {};
26558
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
26559
+
26560
+ if (!viewDoc?.properties) {
26561
+ throw new Error(`view document not found for ${prog_id}`);
26562
+ }
25751
26563
 
25752
- // // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
25753
- // // This ensures the output is always exactly 10 characters long.
25754
- // return shortHash.padStart(10, '0');
25755
- // };
26564
+ const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
26565
+ const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
26566
+ const paramsP = {
26567
+ ...base_params,
26568
+ prog_id,
26569
+ sourceScreenP: null,
26570
+ $callingContainerP: null,
26571
+ triggerIdP: null,
26572
+ callingDataSource_objP: _ds,
26573
+ rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
26574
+ renderType: viewDoc.properties?.renderType,
26575
+ parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
26576
+ source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
26577
+ is_panelP: false,
26578
+ screen_type: options.screen_type || base_params.screen_type || 'render_string',
26579
+ screenInfo: viewDoc,
26580
+ call_screen_propertiesP: base_params.call_screen_propertiesP,
26581
+ parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
26582
+ parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
26583
+ dsSessionP,
26584
+ screenId,
26585
+ containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
26586
+ };
25756
26587
 
25757
- func.common.fastHash = function (inputString) {
25758
- let hash = 0x811c9dc5; // FNV offset basis
26588
+ if (_ds) {
26589
+ _ds.screen_params = paramsP;
26590
+ }
25759
26591
 
25760
- for (let i = 0; i < inputString.length; i++) {
25761
- hash ^= inputString.charCodeAt(i);
25762
- // FNV prime multiplication with 32-bit overflow
25763
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
26592
+ return paramsP;
26593
+ };
26594
+ func.runtime.render.build_prog_tree = async function (options = {}) {
26595
+ const SESSION_ID = options.SESSION_ID;
26596
+ const prog_id = options.prog_id;
26597
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
26598
+
26599
+ if (!viewDoc?.progUi?.length) {
26600
+ throw new Error(`progUi not found for ${prog_id}`);
25764
26601
  }
25765
26602
 
25766
- // Convert to base36 and pad to 10 characters
25767
- return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
26603
+ const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
26604
+ ...options,
26605
+ SESSION_ID,
26606
+ prog_id,
26607
+ viewDoc,
26608
+ }));
26609
+ const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
26610
+ const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
26611
+ const tree = await func.runtime.render.build_tree({
26612
+ SESSION_ID,
26613
+ nodeP: root_node,
26614
+ paramsP,
26615
+ });
26616
+
26617
+ return {
26618
+ tree,
26619
+ paramsP,
26620
+ viewDoc,
26621
+ };
25768
26622
  };
26623
+ func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
26624
+ const runtime_profile = func.runtime.render.get_server_render_mode(options);
26625
+ return {
26626
+ contract: 'xuda.ssr.v1',
26627
+ prog_id: options.prog_id,
26628
+ screenId: render_program.paramsP.screenId,
26629
+ containerId: render_program.paramsP.containerIdP,
26630
+ app_computing_mode: runtime_profile.app_computing_mode,
26631
+ app_render_mode: runtime_profile.app_render_mode,
26632
+ app_client_activation: runtime_profile.app_client_activation,
26633
+ tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
26634
+ };
26635
+ };
26636
+ func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
26637
+ const payload = func.runtime.render.build_ssr_payload(render_program, options);
26638
+ const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
26639
+ const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
26640
+ const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
26641
+
26642
+ 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>`;
26643
+ };
26644
+ func.runtime.render.render_prog_to_string = async function (options = {}) {
26645
+ const render_program = await func.runtime.render.build_prog_tree(options);
26646
+ const html = await func.runtime.render.render_to_string({
26647
+ ...options,
26648
+ SESSION_ID: options.SESSION_ID,
26649
+ treeP: render_program.tree,
26650
+ paramsP: render_program.paramsP,
26651
+ });
26652
+ const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
26653
+ const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
25769
26654
 
25770
- glb.new_xu_render = false;
26655
+ return {
26656
+ prog_id: options.prog_id,
26657
+ dsSessionP: render_program.paramsP.dsSessionP,
26658
+ screenId: render_program.paramsP.screenId,
26659
+ html,
26660
+ screen_html,
26661
+ tree_json: func.runtime.render.serialize_tree(render_program.tree),
26662
+ paramsP: render_program.paramsP,
26663
+ ssr_payload,
26664
+ };
26665
+ };
25771
26666
  func.runtime = func.runtime || {};
25772
26667
  func.runtime.platform = func.runtime.platform || {};
25773
26668
 
@@ -28105,6 +29000,15 @@ func.runtime.ui.ensure_embed_container = function (SESSION_ID) {
28105
29000
  const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
28106
29001
  let $embed_container = func.runtime.ui.find_by_selector($root_element, `#embed_${SESSION_ID}`, true);
28107
29002
 
29003
+ if (!$embed_container.length) {
29004
+ const $ssr_embed_container = func.runtime.ui.find_by_selector($root_element, `[data-xuda-ssr-embed="true"]`, true);
29005
+ const ssr_embed_node = func.runtime.ui.get_first_node($ssr_embed_container);
29006
+ if (ssr_embed_node) {
29007
+ ssr_embed_node.id = 'embed_' + SESSION_ID;
29008
+ $embed_container = func.runtime.ui._wrap_matches([ssr_embed_node]);
29009
+ }
29010
+ }
29011
+
28108
29012
  if (!$embed_container.length) {
28109
29013
  const embed_node = document.createElement('div');
28110
29014
  embed_node.id = 'embed_' + SESSION_ID;
@@ -28140,12 +29044,44 @@ func.runtime.ui.get_root_tag_name = function () {
28140
29044
  }
28141
29045
  return root_tag_name;
28142
29046
  };
29047
+ func.runtime.ui.find_ssr_screen_host = function ($container, screenId, containerId) {
29048
+ const container_node = func.runtime.ui.get_first_node($container);
29049
+ if (!container_node || !screenId) {
29050
+ return null;
29051
+ }
29052
+
29053
+ const dialog_node =
29054
+ container_node.querySelector?.(`#${CSS?.escape ? CSS.escape(screenId) : screenId}`) ||
29055
+ container_node.querySelector?.(`[data-xuda-ssr-screen-id="${screenId}"]`);
29056
+ if (!dialog_node) {
29057
+ return null;
29058
+ }
29059
+
29060
+ const root_frame_node =
29061
+ (containerId ? dialog_node.querySelector?.(`#${CSS?.escape ? CSS.escape(containerId) : containerId}`) : null) ||
29062
+ dialog_node.querySelector?.('[data-xuda-ssr-root-frame="true"]');
29063
+ if (!root_frame_node) {
29064
+ return null;
29065
+ }
29066
+
29067
+ return {
29068
+ $dialogDiv: func.runtime.ui._wrap_matches([dialog_node]),
29069
+ $rootFrame: func.runtime.ui._wrap_matches([root_frame_node]),
29070
+ reused_ssr_host: true,
29071
+ };
29072
+ };
28143
29073
  func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params, $callingContainerP, screenId) {
28144
29074
  var $dialogDiv;
28145
29075
  var $rootFrame;
29076
+ let reused_ssr_host = false;
28146
29077
 
28147
29078
  switch (screen_type) {
28148
29079
  case 'embed': {
29080
+ const ssr_host = func.runtime.ui.find_ssr_screen_host($callingContainerP, screenId, params?.containerIdP);
29081
+ if (ssr_host) {
29082
+ return ssr_host;
29083
+ }
29084
+
28149
29085
  const dialogNode = document.createElement('div');
28150
29086
  dialogNode.id = screenId;
28151
29087
  dialogNode.setAttribute('ui_engine', UI_FRAMEWORK_INSTALLED);
@@ -28195,6 +29131,7 @@ func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params,
28195
29131
  return {
28196
29132
  $dialogDiv,
28197
29133
  $rootFrame,
29134
+ reused_ssr_host,
28198
29135
  };
28199
29136
  };
28200
29137
  func.runtime.ui.find_xu_ui_in_root = function (SESSION_ID, xu_ui_id) {
@@ -28759,6 +29696,11 @@ func.runtime.ui.build_container_xu_data = function (options) {
28759
29696
  func.runtime.ui.apply_container_meta = function ($div, options) {
28760
29697
  const div_node = func.runtime.ui.get_first_node($div);
28761
29698
  func.runtime.ui.set_attr(div_node, 'xu-ui-id', options.ui_id);
29699
+ func.runtime.ui.set_attr(div_node, 'data-xuda-kind', options.treeP?.kind || options.nodeP?.tagName || 'element');
29700
+ func.runtime.ui.set_attr(div_node, 'data-xuda-node-id', options.nodeP?.id || options.nodeP?.id_org || '');
29701
+ if (options.treeP?.meta?.tree_id !== null && typeof options.treeP?.meta?.tree_id !== 'undefined') {
29702
+ func.runtime.ui.set_attr(div_node, 'data-xuda-tree-id', options.treeP.meta.tree_id);
29703
+ }
28762
29704
  const xuData = func.runtime.ui.build_container_xu_data(options);
28763
29705
  if (options.parent_infoP?.iterate_info) {
28764
29706
  xuData.iterate_info = options.parent_infoP.iterate_info;
@@ -28853,6 +29795,38 @@ func.runtime.ui.create_container_element = function (div_typeP, attr_str, prop,
28853
29795
  }
28854
29796
  return func.runtime.ui.create_element(div, attr_str);
28855
29797
  };
29798
+ func.runtime.ui.find_hydration_candidate = function (options) {
29799
+ if (!func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID])) {
29800
+ return null;
29801
+ }
29802
+ if (!func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
29803
+ return null;
29804
+ }
29805
+ if (options.is_placeholder || !options.treeP?.meta?.tree_id) {
29806
+ return null;
29807
+ }
29808
+
29809
+ const append_node = func.runtime.ui.get_first_node(options.$appendTo || options.$container);
29810
+ if (!append_node) {
29811
+ return null;
29812
+ }
29813
+
29814
+ const children = func.runtime.ui.get_children(append_node);
29815
+ for (let index = 0; index < children.length; index++) {
29816
+ const child = children[index];
29817
+ if (child?.__xuda_hydration_claimed) {
29818
+ continue;
29819
+ }
29820
+ if (func.runtime.ui.get_attr(child, 'data-xuda-tree-id') !== `${options.treeP.meta.tree_id}`) {
29821
+ continue;
29822
+ }
29823
+ child.__xuda_hydration_claimed = true;
29824
+ func.runtime.ui.set_attr(child, 'data-xuda-client-activation', 'hydrate');
29825
+ return child;
29826
+ }
29827
+
29828
+ return null;
29829
+ };
28856
29830
  func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
28857
29831
  const nodeId = nodeP.xu_tree_id || nodeP.id;
28858
29832
  const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
@@ -28905,7 +29879,11 @@ func.runtime.ui.create_container = async function (options) {
28905
29879
  try {
28906
29880
  const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
28907
29881
  const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
28908
- const $div = func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
29882
+ const hydration_candidate = func.runtime.ui.find_hydration_candidate({
29883
+ ...options,
29884
+ $appendTo,
29885
+ });
29886
+ const $div = hydration_candidate || func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
28909
29887
  const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
28910
29888
  container_xu_data,
28911
29889
  currentRecordId,
@@ -28931,9 +29909,10 @@ func.runtime.ui.create_container = async function (options) {
28931
29909
  parent_infoP: options.parent_infoP,
28932
29910
  is_placeholder: options.is_placeholder,
28933
29911
  classP: options.classP,
29912
+ treeP: options.treeP,
28934
29913
  });
28935
29914
 
28936
- if (options.div_typeP !== 'svg') {
29915
+ if (!hydration_candidate && options.div_typeP !== 'svg') {
28937
29916
  func.runtime.ui.append_to($div, $appendTo);
28938
29917
  }
28939
29918
  return $div;
@@ -31614,9 +32593,10 @@ func.runtime.ui.init_screen = async function (options) {
31614
32593
 
31615
32594
  const _session = SESSION_OBJ[SESSION_ID];
31616
32595
  const screenInfo = structuredClone(screen_ret);
32596
+ const ssr_payload = func.runtime.render.should_use_ssr_payload(SESSION_ID, { prog_id }) ? func.runtime.render.get_ssr_payload(_session) : null;
31617
32597
 
31618
32598
  const screen_type = source_functionP?.split('_')?.[1];
31619
- const screenId = (glb.screen_num++).toString();
32599
+ const screenId = ssr_payload?.screenId || (glb.screen_num++).toString();
31620
32600
 
31621
32601
  if (SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
31622
32602
  const wait_for_SCREEN_BLOCKER_release = function () {
@@ -31658,6 +32638,8 @@ func.runtime.ui.init_screen = async function (options) {
31658
32638
  call_screen_propertiesP,
31659
32639
  parentDataSourceNoP: _session.DS_GLB?.[callingDataSource_objP?.dsSession]?.dsSession || callingDataSource_objP?.parentDataSourceNo || 0,
31660
32640
  parameters_raw_obj,
32641
+ containerIdP: ssr_payload?.containerId || null,
32642
+ ssr_payload,
31661
32643
  };
31662
32644
 
31663
32645
  const screen_host = func.runtime.ui.create_screen_host(SESSION_ID, screen_type, params, $callingContainerP, screenId);
@@ -31690,6 +32672,10 @@ func.runtime.ui.init_screen = async function (options) {
31690
32672
  func.runtime.ui.set_style($rootFrame, 'display', 'contents');
31691
32673
  }
31692
32674
 
32675
+ if (screen_host.reused_ssr_host && func.runtime.render.is_takeover_mode(_session)) {
32676
+ func.runtime.ui.empty($rootFrame);
32677
+ }
32678
+
31693
32679
  if (!is_panelP) func.UI.utils.indicator.screen.busy();
31694
32680
 
31695
32681
  const ret = await func.datasource.create(
@@ -31731,7 +32717,24 @@ func.runtime.ui.init_screen = async function (options) {
31731
32717
  }
31732
32718
  let node = structuredClone(viewDoc.progUi);
31733
32719
  if (!node.length) return console.warn('ui node empty');
31734
- 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);
32720
+ const root_tree = await func.runtime.render.build_tree({
32721
+ SESSION_ID,
32722
+ nodeP: node[0],
32723
+ paramsP: params,
32724
+ });
32725
+ const ret_render_$container = await func.runtime.render.render_tree(root_tree, {
32726
+ SESSION_ID,
32727
+ $container: $rootFrame,
32728
+ parent_infoP: null,
32729
+ paramsP: params,
32730
+ jobNoP,
32731
+ is_skeleton: null,
32732
+ keyP: null,
32733
+ refreshed_ds: null,
32734
+ parent_nodeP: null,
32735
+ check_existP: null,
32736
+ $root_container: $rootFrame,
32737
+ });
31735
32738
 
31736
32739
  if (!is_panelP) func.UI.utils.indicator.screen.normal();
31737
32740
 
@@ -32156,6 +33159,7 @@ func.runtime.ui.render_single_view_node = async function (options) {
32156
33159
  parent_infoP: options.parent_infoP,
32157
33160
  jobNoP: options.jobNoP,
32158
33161
  keyP: options.keyP,
33162
+ treeP: options.treeP,
32159
33163
  parent_nodeP: options.parent_nodeP,
32160
33164
  prop: options.prop,
32161
33165
  div_typeP: 'div',
@@ -32284,6 +33288,7 @@ func.runtime.ui.render_panel_node = async function (options) {
32284
33288
  parent_infoP: options.parent_infoP,
32285
33289
  jobNoP: options.jobNoP,
32286
33290
  keyP: options.keyP,
33291
+ treeP: options.treeP,
32287
33292
  parent_nodeP: options.parent_nodeP,
32288
33293
  prop: options.prop,
32289
33294
  $appendToP: $wrapper,
@@ -32613,6 +33618,14 @@ func.runtime.ui.screen_loading_done = async function (options) {
32613
33618
  });
32614
33619
 
32615
33620
  const _session = SESSION_OBJ[options.SESSION_ID];
33621
+ if (func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
33622
+ const root_node = func.runtime.ui.get_root_node(options.SESSION_ID);
33623
+ if (root_node) {
33624
+ func.runtime.ui.set_attr(root_node, 'data-xuda-client-activation', _session.opt.app_client_activation || 'none');
33625
+ func.runtime.ui.set_attr(root_node, 'data-xuda-ssr-status', _session.opt.app_client_activation === 'hydrate' ? 'hydrated' : 'taken-over');
33626
+ }
33627
+ func.runtime.render.mark_ssr_payload_consumed(options.SESSION_ID);
33628
+ }
32616
33629
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
32617
33630
  func.UI.utils.screen_blocker(false, options.paramsP.prog_id + (options.paramsP.sourceScreenP ? '_' + options.paramsP.sourceScreenP : ''));
32618
33631
  if (_session.prog_id === options.paramsP.prog_id) {
@@ -34455,6 +35468,9 @@ func.runtime.render.get_screen_context = function (SESSION_ID, $container, param
34455
35468
  };
34456
35469
  func.runtime.render.get_node_attributes = function (nodeP) {
34457
35470
  try {
35471
+ if (func.runtime.render.is_tree_node?.(nodeP)) {
35472
+ return nodeP.attributes;
35473
+ }
34458
35474
  return nodeP?.attributes;
34459
35475
  } catch (error) {
34460
35476
  return undefined;
@@ -34572,6 +35588,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
34572
35588
  parent_infoP: options.parent_infoP,
34573
35589
  jobNoP: options.jobNoP,
34574
35590
  keyP: options.keyP,
35591
+ treeP: options.treeP,
34575
35592
  parent_nodeP: options.parent_nodeP,
34576
35593
  prop: options.prop,
34577
35594
  div_typeP: options.element,
@@ -34594,6 +35611,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
34594
35611
  parent_infoP: options.parent_infoP,
34595
35612
  jobNoP: options.jobNoP,
34596
35613
  keyP: options.keyP,
35614
+ treeP: options.treeP,
34597
35615
  parent_nodeP: options.parent_nodeP,
34598
35616
  prop: options.prop,
34599
35617
  div_typeP: options.element,
@@ -35450,11 +36468,16 @@ func.runtime.widgets.render_node = async function (options) {
35450
36468
  parent_infoP: options.parent_infoP,
35451
36469
  jobNoP: options.jobNoP,
35452
36470
  keyP: options.keyP,
36471
+ treeP: options.treeP,
35453
36472
  parent_nodeP: options.parent_nodeP,
35454
36473
  prop: options.prop,
35455
36474
  classP: 'widget_wrapper',
35456
36475
  });
35457
36476
 
36477
+ if (func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID]) && func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
36478
+ func.runtime.ui.empty($div);
36479
+ }
36480
+
35458
36481
  const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, options.prop);
35459
36482
  const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
35460
36483
  const report_error = function (descP, warn) {
@@ -35557,9 +36580,306 @@ func.runtime.widgets = func.runtime.widgets || {};
35557
36580
 
35558
36581
  // Browser-only special node renderers live here so the core render tree can stay focused.
35559
36582
 
36583
+ const normalize_runtime_tag_name = function (tag_name) {
36584
+ return `${tag_name || ''}`.trim().toLowerCase();
36585
+ };
36586
+
36587
+ const get_runtime_node_attributes = function (nodeP) {
36588
+ if (!nodeP?.attributes || typeof nodeP.attributes !== 'object') {
36589
+ return {};
36590
+ }
36591
+
36592
+ return nodeP.attributes;
36593
+ };
36594
+
36595
+ const get_runtime_node_content = function (nodeP) {
36596
+ if (typeof nodeP?.content === 'string') {
36597
+ return nodeP.content;
36598
+ }
36599
+
36600
+ if (typeof nodeP?.text === 'string') {
36601
+ return nodeP.text;
36602
+ }
36603
+
36604
+ if (!Array.isArray(nodeP?.children)) {
36605
+ return '';
36606
+ }
36607
+
36608
+ return nodeP.children
36609
+ .map(function (child) {
36610
+ if (typeof child === 'string') {
36611
+ return child;
36612
+ }
36613
+ if (typeof child?.content === 'string') {
36614
+ return child.content;
36615
+ }
36616
+ if (typeof child?.text === 'string') {
36617
+ return child.text;
36618
+ }
36619
+ return '';
36620
+ })
36621
+ .join('');
36622
+ };
36623
+
36624
+ const get_runtime_asset_key = function (options, tag_name) {
36625
+ const source_node = options.treeP || options.nodeP || {};
36626
+ const parts = [
36627
+ 'xuda-html-asset',
36628
+ options.paramsP?.prog_id || '',
36629
+ tag_name,
36630
+ source_node.id || source_node.id_org || '',
36631
+ typeof options.keyP === 'undefined' || options.keyP === null ? '' : `${options.keyP}`,
36632
+ ].filter(function (part) {
36633
+ return `${part || ''}`.trim() !== '';
36634
+ });
36635
+
36636
+ return parts.join(':');
36637
+ };
36638
+
36639
+ const get_runtime_asset_signature = function (attributes, content) {
36640
+ const normalized_attributes = {};
36641
+ const attr_keys = Object.keys(attributes || {}).sort();
36642
+
36643
+ for (let index = 0; index < attr_keys.length; index++) {
36644
+ const key = attr_keys[index];
36645
+ normalized_attributes[key] = attributes[key];
36646
+ }
36647
+
36648
+ return JSON.stringify({
36649
+ attributes: normalized_attributes,
36650
+ content: `${content || ''}`,
36651
+ });
36652
+ };
36653
+
36654
+ const escape_runtime_asset_selector_value = function (value) {
36655
+ const win = func.runtime.platform.get_window?.();
36656
+ if (win?.CSS?.escape) {
36657
+ return win.CSS.escape(value);
36658
+ }
36659
+
36660
+ return `${value || ''}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
36661
+ };
36662
+
36663
+ const find_runtime_head_asset = function (head, tag_name, asset_key) {
36664
+ if (!head?.querySelector || !asset_key) {
36665
+ return null;
36666
+ }
36667
+
36668
+ return head.querySelector(`${tag_name}[data-xuda-asset-key="${escape_runtime_asset_selector_value(asset_key)}"]`);
36669
+ };
36670
+
36671
+ const find_runtime_head_asset_by_attr = function (head, tag_name, attr_name, attr_value) {
36672
+ if (!head?.querySelectorAll || !attr_name || !attr_value) {
36673
+ return null;
36674
+ }
36675
+
36676
+ const candidates = Array.from(head.querySelectorAll(tag_name));
36677
+ return (
36678
+ candidates.find(function (candidate) {
36679
+ return candidate.getAttribute(attr_name) === attr_value || candidate[attr_name] === attr_value;
36680
+ }) || null
36681
+ );
36682
+ };
36683
+
36684
+ const wait_for_runtime_asset_load = function (node) {
36685
+ if (!node?.addEventListener) {
36686
+ return Promise.resolve(node);
36687
+ }
36688
+
36689
+ if (node.getAttribute?.('data-xuda-loaded') === 'true') {
36690
+ return Promise.resolve(node);
36691
+ }
36692
+
36693
+ if (!node.getAttribute?.('data-xuda-asset-key')) {
36694
+ return Promise.resolve(node);
36695
+ }
36696
+
36697
+ const tag_name = normalize_runtime_tag_name(node.tagName);
36698
+ if (tag_name !== 'script' && !(tag_name === 'link' && normalize_runtime_tag_name(node.getAttribute?.('rel')) === 'stylesheet')) {
36699
+ return Promise.resolve(node);
36700
+ }
36701
+
36702
+ return new Promise(function (resolve) {
36703
+ const done = function () {
36704
+ node.setAttribute?.('data-xuda-loaded', 'true');
36705
+ resolve(node);
36706
+ };
36707
+
36708
+ node.addEventListener('load', done, { once: true });
36709
+ node.addEventListener('error', done, { once: true });
36710
+ });
36711
+ };
36712
+
36713
+ const remove_runtime_head_asset = function (node) {
36714
+ if (node?.parentNode?.removeChild) {
36715
+ node.parentNode.removeChild(node);
36716
+ }
36717
+ };
36718
+
36719
+ const apply_runtime_asset_metadata = function (node, asset_key, signature) {
36720
+ if (!node?.setAttribute) {
36721
+ return node;
36722
+ }
36723
+
36724
+ if (asset_key) {
36725
+ node.setAttribute('data-xuda-asset-key', asset_key);
36726
+ }
36727
+ node.setAttribute('data-xuda-asset-signature', signature);
36728
+ return node;
36729
+ };
36730
+
36731
+ const create_runtime_head_element = function (doc, tag_name, attributes, asset_key, signature) {
36732
+ const node = doc.createElement(tag_name);
36733
+ apply_runtime_asset_metadata(node, asset_key, signature);
36734
+ func.runtime.platform.apply_element_attributes(node, attributes);
36735
+ return node;
36736
+ };
36737
+
36738
+ const upsert_runtime_head_element = async function (options) {
36739
+ const doc = func.runtime.platform.get_document?.();
36740
+ const head = doc?.head;
36741
+ const tag_name = normalize_runtime_tag_name(options.tag_name);
36742
+
36743
+ if (!doc?.createElement || !head?.appendChild || !tag_name) {
36744
+ return null;
36745
+ }
36746
+
36747
+ const asset_key = options.asset_key || '';
36748
+ const signature = options.signature || '';
36749
+ const attributes = options.attributes || {};
36750
+ const content = typeof options.content === 'string' ? options.content : '';
36751
+
36752
+ const existing_by_key = find_runtime_head_asset(head, tag_name, asset_key);
36753
+ if (existing_by_key && existing_by_key.getAttribute('data-xuda-asset-signature') === signature) {
36754
+ return options.await_load ? await wait_for_runtime_asset_load(existing_by_key) : existing_by_key;
36755
+ }
36756
+
36757
+ if (existing_by_key) {
36758
+ remove_runtime_head_asset(existing_by_key);
36759
+ }
36760
+
36761
+ if (options.find_existing_attr?.name && options.find_existing_attr?.value) {
36762
+ const existing_by_attr = find_runtime_head_asset_by_attr(head, tag_name, options.find_existing_attr.name, options.find_existing_attr.value);
36763
+ if (existing_by_attr) {
36764
+ apply_runtime_asset_metadata(existing_by_attr, asset_key, signature);
36765
+ existing_by_attr.setAttribute?.('data-xuda-loaded', 'true');
36766
+ return existing_by_attr;
36767
+ }
36768
+ }
36769
+
36770
+ const node = create_runtime_head_element(doc, tag_name, attributes, asset_key, signature);
36771
+
36772
+ if (tag_name === 'script' && attributes.src && !Object.prototype.hasOwnProperty.call(attributes, 'async')) {
36773
+ const script_type = normalize_runtime_tag_name(attributes.type);
36774
+ if (script_type !== 'module') {
36775
+ node.async = false;
36776
+ }
36777
+ }
36778
+
36779
+ if (tag_name === 'style' || (tag_name === 'script' && !attributes.src)) {
36780
+ node.textContent = content;
36781
+ }
36782
+
36783
+ head.appendChild(node);
36784
+
36785
+ if (tag_name !== 'script' && tag_name !== 'link') {
36786
+ node.setAttribute('data-xuda-loaded', 'true');
36787
+ }
36788
+
36789
+ return options.await_load ? await wait_for_runtime_asset_load(node) : node;
36790
+ };
36791
+
36792
+ const render_runtime_html_asset = async function (options) {
36793
+ if (options.is_skeleton) {
36794
+ return options.$container;
36795
+ }
36796
+
36797
+ const nodeP = options.treeP || options.nodeP || {};
36798
+ const tag_name = normalize_runtime_tag_name(nodeP.tagName);
36799
+ const attributes = { ...get_runtime_node_attributes(nodeP) };
36800
+ const content = get_runtime_node_content(nodeP);
36801
+ const asset_key = get_runtime_asset_key(options, tag_name);
36802
+ const signature = get_runtime_asset_signature(attributes, content);
36803
+
36804
+ switch (tag_name) {
36805
+ case 'title':
36806
+ func.runtime.platform.set_title(content);
36807
+ return options.$container;
36808
+ case 'style':
36809
+ await upsert_runtime_head_element({
36810
+ tag_name,
36811
+ attributes,
36812
+ content,
36813
+ asset_key,
36814
+ signature,
36815
+ });
36816
+ return options.$container;
36817
+ case 'meta':
36818
+ if (!Object.keys(attributes).length) {
36819
+ return options.$container;
36820
+ }
36821
+ await upsert_runtime_head_element({
36822
+ tag_name,
36823
+ attributes,
36824
+ asset_key,
36825
+ signature,
36826
+ });
36827
+ return options.$container;
36828
+ case 'link': {
36829
+ const href = `${attributes.href || ''}`.trim();
36830
+ if (!href) {
36831
+ return options.$container;
36832
+ }
36833
+ await upsert_runtime_head_element({
36834
+ tag_name,
36835
+ attributes,
36836
+ asset_key,
36837
+ signature,
36838
+ find_existing_attr: {
36839
+ name: 'href',
36840
+ value: href,
36841
+ },
36842
+ await_load: normalize_runtime_tag_name(attributes.rel) === 'stylesheet',
36843
+ });
36844
+ return options.$container;
36845
+ }
36846
+ case 'script': {
36847
+ const src = `${attributes.src || ''}`.trim();
36848
+ if (!src && !content.trim()) {
36849
+ return options.$container;
36850
+ }
36851
+ await upsert_runtime_head_element({
36852
+ tag_name,
36853
+ attributes,
36854
+ content,
36855
+ asset_key,
36856
+ signature,
36857
+ find_existing_attr: src
36858
+ ? {
36859
+ name: 'src',
36860
+ value: src,
36861
+ }
36862
+ : null,
36863
+ await_load: !!src,
36864
+ });
36865
+ return options.$container;
36866
+ }
36867
+ default:
36868
+ return null;
36869
+ }
36870
+ };
36871
+
35560
36872
  func.runtime.render.render_special_node = async function (options) {
35561
- if (options.nodeP.content && options.nodeP.attributes) {
35562
- options.nodeP.attributes['xu-content'] = options.nodeP.content;
36873
+ const treeP = options.treeP || null;
36874
+ const nodeP = options.nodeP || func.runtime.render.get_tree_source_node(treeP);
36875
+ const render_tag_name = treeP?.tagName || nodeP?.tagName;
36876
+ const normalized_render_tag_name = normalize_runtime_tag_name(render_tag_name);
36877
+ const is_native_html_asset = ['title', 'style', 'meta', 'link', 'script'].includes(normalized_render_tag_name);
36878
+
36879
+ if (!is_native_html_asset && treeP?.content && nodeP?.attributes) {
36880
+ nodeP.attributes['xu-content'] = treeP.content;
36881
+ } else if (!is_native_html_asset && nodeP?.content && nodeP.attributes) {
36882
+ nodeP.attributes['xu-content'] = nodeP.content;
35563
36883
  }
35564
36884
 
35565
36885
  const renderers = {
@@ -35569,6 +36889,7 @@ func.runtime.render.render_special_node = async function (options) {
35569
36889
  SESSION_ID: options.SESSION_ID,
35570
36890
  $container: options.$container,
35571
36891
  $root_container: options.$root_container,
36892
+ treeP,
35572
36893
  nodeP: options.nodeP,
35573
36894
  parent_infoP: options.parent_infoP,
35574
36895
  paramsP: options.paramsP,
@@ -35585,6 +36906,7 @@ func.runtime.render.render_special_node = async function (options) {
35585
36906
  SESSION_ID: options.SESSION_ID,
35586
36907
  $container: options.$container,
35587
36908
  $root_container: options.$root_container,
36909
+ treeP,
35588
36910
  nodeP: options.nodeP,
35589
36911
  parent_infoP: options.parent_infoP,
35590
36912
  paramsP: options.paramsP,
@@ -35604,6 +36926,7 @@ func.runtime.render.render_special_node = async function (options) {
35604
36926
  SESSION_ID: options.SESSION_ID,
35605
36927
  $container: options.$container,
35606
36928
  $root_container: options.$root_container,
36929
+ treeP,
35607
36930
  nodeP: options.nodeP,
35608
36931
  parent_infoP: options.parent_infoP,
35609
36932
  paramsP: options.paramsP,
@@ -35620,6 +36943,7 @@ func.runtime.render.render_special_node = async function (options) {
35620
36943
  SESSION_ID: options.SESSION_ID,
35621
36944
  $container: options.$container,
35622
36945
  $root_container: options.$root_container,
36946
+ treeP,
35623
36947
  nodeP: options.nodeP,
35624
36948
  parent_infoP: options.parent_infoP,
35625
36949
  paramsP: options.paramsP,
@@ -35631,9 +36955,24 @@ func.runtime.render.render_special_node = async function (options) {
35631
36955
  refreshed_ds: options.refreshed_ds,
35632
36956
  });
35633
36957
  },
36958
+ title: async function () {
36959
+ return await render_runtime_html_asset(options);
36960
+ },
36961
+ style: async function () {
36962
+ return await render_runtime_html_asset(options);
36963
+ },
36964
+ meta: async function () {
36965
+ return await render_runtime_html_asset(options);
36966
+ },
36967
+ link: async function () {
36968
+ return await render_runtime_html_asset(options);
36969
+ },
36970
+ script: async function () {
36971
+ return await render_runtime_html_asset(options);
36972
+ },
35634
36973
  };
35635
36974
 
35636
- const renderer = renderers[options.nodeP.tagName];
36975
+ const renderer = renderers[normalized_render_tag_name];
35637
36976
  if (!renderer) {
35638
36977
  return { handled: false };
35639
36978
  }
@@ -35747,8 +37086,9 @@ func.runtime.widgets = func.runtime.widgets || {};
35747
37086
  // Browser-only render tree entrypoints live here so draw/cache helpers can stay focused.
35748
37087
 
35749
37088
  func.runtime.render.create_tree_runtime = function (options) {
37089
+ const render_node = options.nodeP || func.runtime.render.get_tree_source_node(options.treeP);
35750
37090
  const render_context = func.runtime.render.get_screen_context(options.SESSION_ID, options.$container, options.paramsP, options.is_skeleton);
35751
- const prop = func.runtime.render.get_node_attributes(options.nodeP);
37091
+ const prop = func.runtime.render.get_node_attributes(options.treeP || render_node);
35752
37092
  const is_mobile = render_context.is_mobile ? true : false;
35753
37093
  const hover_handlers = func.runtime.render.create_hover_handlers({
35754
37094
  SESSION_ID: options.SESSION_ID,
@@ -35763,13 +37103,22 @@ func.runtime.render.create_tree_runtime = function (options) {
35763
37103
  return await func.runtime.ui.close_modal_session(options.SESSION_ID, modal_id);
35764
37104
  };
35765
37105
  const iterate_child = async function ($divP, nodeP, parent_infoP, $root_container, before_record_function) {
37106
+ const child_tree = await func.runtime.render.ensure_tree_node({
37107
+ SESSION_ID: options.SESSION_ID,
37108
+ nodeP: nodeP || options.treeP || render_node,
37109
+ parent_infoP,
37110
+ paramsP: options.paramsP,
37111
+ keyP: options.keyP,
37112
+ parent_nodeP: render_node,
37113
+ pathP: options.treeP?.meta?.path || [],
37114
+ });
35766
37115
  return await func.runtime.render.iterate_children({
35767
37116
  $divP,
35768
- nodeP,
37117
+ nodeP: child_tree,
35769
37118
  is_mobile,
35770
37119
  before_record_function,
35771
37120
  render_child: async function (key, child) {
35772
- await options.render_child($divP, child, parent_infoP, key, nodeP, $root_container);
37121
+ await options.render_child($divP, child, parent_infoP, key, render_node, $root_container);
35773
37122
  },
35774
37123
  });
35775
37124
  };
@@ -35799,7 +37148,7 @@ func.runtime.render.draw_node = async function (options) {
35799
37148
  check_existP: options.check_existP,
35800
37149
  $root_container: options.$root_container,
35801
37150
  prop: options.prop,
35802
- element: options.nodeP.tagName,
37151
+ element: options.treeP?.tagName || options.nodeP.tagName,
35803
37152
  hover_handlers: options.hover_handlers,
35804
37153
  include_hover_click: options.include_hover_click,
35805
37154
  iterate_child: options.iterate_child,
@@ -35822,21 +37171,33 @@ func.runtime.render.draw_node = async function (options) {
35822
37171
  nodeP: options.nodeP,
35823
37172
  });
35824
37173
  };
35825
- 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) {
35826
- if (!nodeP) return;
35827
- const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'render_ui_tree');
35828
- func.runtime?.perf?.increment_map?.(SESSION_ID, 'render_node_counts', nodeP.id || nodeP.id_org || nodeP.tagName || 'unknown');
37174
+ func.runtime.render.render_tree = async function (treeP, renderer_context) {
37175
+ if (!treeP) return;
37176
+ const nodeP = func.runtime.render.get_tree_source_node(treeP);
37177
+ const perf_end = func.runtime?.perf?.start?.(renderer_context.SESSION_ID, 'render_ui_tree');
37178
+ func.runtime?.perf?.increment_map?.(renderer_context.SESSION_ID, 'render_node_counts', treeP.id || nodeP?.id || nodeP?.id_org || treeP.tagName || 'unknown');
35829
37179
  try {
35830
37180
  const tree_runtime = func.runtime.render.create_tree_runtime({
35831
- SESSION_ID,
35832
- $container,
37181
+ SESSION_ID: renderer_context.SESSION_ID,
37182
+ $container: renderer_context.$container,
37183
+ treeP,
35833
37184
  nodeP,
35834
- parent_infoP,
35835
- paramsP,
35836
- jobNoP,
35837
- is_skeleton,
37185
+ parent_infoP: renderer_context.parent_infoP,
37186
+ paramsP: renderer_context.paramsP,
37187
+ jobNoP: renderer_context.jobNoP,
37188
+ is_skeleton: renderer_context.is_skeleton,
37189
+ keyP: renderer_context.keyP,
35838
37190
  render_child: async function ($divP, child, parent_infoP, key, parentNodeP, rootContainerP) {
35839
- await func.runtime.render.render_ui_tree(SESSION_ID, $divP, child, parent_infoP, paramsP, jobNoP, is_skeleton, key, null, parentNodeP, null, rootContainerP);
37191
+ await func.runtime.render.render_tree(child, {
37192
+ ...renderer_context,
37193
+ $container: $divP,
37194
+ parent_infoP,
37195
+ keyP: key,
37196
+ refreshed_ds: null,
37197
+ parent_nodeP: parentNodeP,
37198
+ check_existP: null,
37199
+ $root_container: rootContainerP,
37200
+ });
35840
37201
  },
35841
37202
  });
35842
37203
  const render_context = tree_runtime.render_context;
@@ -35848,23 +37209,24 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
35848
37209
  const iterate_child = tree_runtime.iterate_child;
35849
37210
 
35850
37211
  func.runtime.render.log_tree_debug({
35851
- SESSION_ID,
35852
- paramsP,
37212
+ SESSION_ID: renderer_context.SESSION_ID,
37213
+ paramsP: renderer_context.paramsP,
35853
37214
  nodeP,
35854
37215
  _ds,
35855
37216
  });
35856
37217
  const special_render = await func.runtime.render.render_special_node({
35857
- SESSION_ID,
35858
- $container,
35859
- $root_container,
37218
+ SESSION_ID: renderer_context.SESSION_ID,
37219
+ $container: renderer_context.$container,
37220
+ $root_container: renderer_context.$root_container,
37221
+ treeP,
35860
37222
  nodeP,
35861
- parent_infoP,
35862
- paramsP,
35863
- jobNoP,
35864
- is_skeleton,
35865
- keyP,
35866
- refreshed_ds,
35867
- parent_nodeP,
37223
+ parent_infoP: renderer_context.parent_infoP,
37224
+ paramsP: renderer_context.paramsP,
37225
+ jobNoP: renderer_context.jobNoP,
37226
+ is_skeleton: renderer_context.is_skeleton,
37227
+ keyP: renderer_context.keyP,
37228
+ refreshed_ds: renderer_context.refreshed_ds,
37229
+ parent_nodeP: renderer_context.parent_nodeP,
35868
37230
  prop,
35869
37231
  render_context,
35870
37232
  hover_handlers,
@@ -35872,22 +37234,23 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
35872
37234
  close_modal,
35873
37235
  });
35874
37236
  if (special_render.handled) {
35875
- func.runtime?.perf?.increment?.(SESSION_ID, 'render_special_node_hits');
37237
+ func.runtime?.perf?.increment?.(renderer_context.SESSION_ID, 'render_special_node_hits');
35876
37238
  return special_render.result;
35877
37239
  }
35878
37240
  return await func.runtime.render.draw_node({
35879
- SESSION_ID,
35880
- $container,
35881
- $root_container,
37241
+ SESSION_ID: renderer_context.SESSION_ID,
37242
+ $container: renderer_context.$container,
37243
+ $root_container: renderer_context.$root_container,
37244
+ treeP,
35882
37245
  nodeP,
35883
- parent_infoP,
35884
- paramsP,
35885
- jobNoP,
35886
- is_skeleton,
35887
- keyP,
35888
- refreshed_ds,
35889
- parent_nodeP,
35890
- check_existP,
37246
+ parent_infoP: renderer_context.parent_infoP,
37247
+ paramsP: renderer_context.paramsP,
37248
+ jobNoP: renderer_context.jobNoP,
37249
+ is_skeleton: renderer_context.is_skeleton,
37250
+ keyP: renderer_context.keyP,
37251
+ refreshed_ds: renderer_context.refreshed_ds,
37252
+ parent_nodeP: renderer_context.parent_nodeP,
37253
+ check_existP: renderer_context.check_existP,
35891
37254
  prop,
35892
37255
  hover_handlers,
35893
37256
  include_hover_click,
@@ -35896,6 +37259,31 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
35896
37259
  } finally {
35897
37260
  perf_end?.();
35898
37261
  }
37262
+ };
37263
+ 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) {
37264
+ if (!nodeP) return;
37265
+ const treeP = await func.runtime.render.ensure_tree_node({
37266
+ SESSION_ID,
37267
+ nodeP,
37268
+ parent_infoP,
37269
+ paramsP,
37270
+ keyP,
37271
+ parent_nodeP,
37272
+ });
37273
+
37274
+ return await func.runtime.render.render_tree(treeP, {
37275
+ SESSION_ID,
37276
+ $container,
37277
+ parent_infoP,
37278
+ paramsP,
37279
+ jobNoP,
37280
+ is_skeleton,
37281
+ keyP,
37282
+ refreshed_ds,
37283
+ parent_nodeP,
37284
+ check_existP,
37285
+ $root_container,
37286
+ });
35899
37287
  };
35900
37288
  func.runtime = func.runtime || {};
35901
37289
  func.runtime.ui = func.runtime.ui || {};
@@ -37419,6 +38807,7 @@ func.runtime.render.handle_xu_panel_program = async function (options) {
37419
38807
  parent_infoP: options.parent_infoP,
37420
38808
  jobNoP: options.jobNoP,
37421
38809
  keyP: options.keyP,
38810
+ treeP: options.treeP,
37422
38811
  parent_nodeP: options.parent_nodeP,
37423
38812
  prop: options.nodeP.attributes,
37424
38813
  $appendToP: $wrapper,
@@ -44969,6 +46358,12 @@ function xuda(...args) {
44969
46358
  if (typeof opt !== 'object') {
44970
46359
  return console.error('Xuda Error - opt argument is not an object');
44971
46360
  }
46361
+
46362
+ if (!opt.ssr_payload && func.runtime.platform.get_window()?.__XUDA_SSR__) {
46363
+ opt.ssr_payload = func.runtime.platform.get_window().__XUDA_SSR__;
46364
+ }
46365
+ func.runtime.render.apply_runtime_bootstrap_defaults(opt);
46366
+
44972
46367
  glb.URL_PARAMS = func.common.getJsonFromUrl(platform.get_url_href());
44973
46368
 
44974
46369
  glb.worker_type = 'Worker';