@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.
@@ -1779,7 +1779,30 @@ func.runtime.platform.emit = function (name, data) {
1779
1779
  };
1780
1780
 
1781
1781
  // ── Platform helpers for DOM-independent resource loading ──
1782
- func.runtime.platform.load_script = function (url, type, callback) {
1782
+ func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
1783
+ if (!node?.setAttribute || !attributes) {
1784
+ return node;
1785
+ }
1786
+
1787
+ const excluded = new Set(excluded_keys || []);
1788
+ const attr_keys = Object.keys(attributes);
1789
+ for (let index = 0; index < attr_keys.length; index++) {
1790
+ const key = attr_keys[index];
1791
+ if (!key || excluded.has(key)) {
1792
+ continue;
1793
+ }
1794
+
1795
+ const value = attributes[key];
1796
+ if (value === false || typeof value === 'undefined') {
1797
+ continue;
1798
+ }
1799
+
1800
+ node.setAttribute(key, value === null ? '' : `${value}`);
1801
+ }
1802
+
1803
+ return node;
1804
+ };
1805
+ func.runtime.platform.load_script = function (url, type, callback, attributes) {
1783
1806
  const doc = func.runtime.platform.get_document();
1784
1807
  if (!doc?.createElement || !doc?.head?.appendChild) {
1785
1808
  if (callback) {
@@ -1787,19 +1810,63 @@ func.runtime.platform.load_script = function (url, type, callback) {
1787
1810
  }
1788
1811
  return;
1789
1812
  }
1813
+ const find_existing_script = function () {
1814
+ const asset_key = attributes?.['data-xuda-asset-key'];
1815
+ const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
1816
+ return scripts.find(function (script) {
1817
+ if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
1818
+ return true;
1819
+ }
1820
+ return !!(url && script.getAttribute('src') === url);
1821
+ }) || null;
1822
+ };
1823
+
1824
+ const existing_script = find_existing_script();
1825
+ if (existing_script) {
1826
+ if (callback) {
1827
+ if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
1828
+ callback();
1829
+ } else {
1830
+ existing_script.addEventListener('load', callback, { once: true });
1831
+ existing_script.addEventListener('error', callback, { once: true });
1832
+ }
1833
+ }
1834
+ return existing_script;
1835
+ }
1836
+
1790
1837
  const script = doc.createElement('script');
1791
1838
  script.src = url;
1792
1839
  if (type) script.type = type;
1793
- script.onload = callback;
1840
+ func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
1841
+ script.onload = function () {
1842
+ script.setAttribute('data-xuda-loaded', 'true');
1843
+ if (callback) {
1844
+ callback();
1845
+ }
1846
+ };
1847
+ script.onerror = function () {
1848
+ if (callback) {
1849
+ callback();
1850
+ }
1851
+ };
1794
1852
  doc.head.appendChild(script);
1853
+ return script;
1795
1854
  };
1796
- func.runtime.platform.load_css = function (href) {
1855
+ func.runtime.platform.load_css = function (href, attributes) {
1797
1856
  const doc = func.runtime.platform.get_document();
1798
1857
  if (!doc?.createElement || !doc?.head) {
1799
1858
  return;
1800
1859
  }
1801
1860
  try {
1802
- if (doc.querySelector('link[href="' + href + '"]')) return;
1861
+ const asset_key = attributes?.['data-xuda-asset-key'];
1862
+ const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
1863
+ const existing = existing_links.find(function (link) {
1864
+ if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
1865
+ return true;
1866
+ }
1867
+ return !!(href && link.getAttribute('href') === href);
1868
+ });
1869
+ if (existing) return existing;
1803
1870
  } catch (err) {
1804
1871
  return;
1805
1872
  }
@@ -1807,7 +1874,9 @@ func.runtime.platform.load_css = function (href) {
1807
1874
  link.rel = 'stylesheet';
1808
1875
  link.type = 'text/css';
1809
1876
  link.href = href;
1877
+ func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
1810
1878
  doc.head.insertBefore(link, doc.head.firstChild);
1879
+ return link;
1811
1880
  };
1812
1881
  func.runtime.platform.remove_js_css = function (filename, filetype) {
1813
1882
  const doc = func.runtime.platform.get_document();
@@ -2098,6 +2167,147 @@ func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_q
2098
2167
  delete registry_entry.promise_queue[promise_queue_id];
2099
2168
  return true;
2100
2169
  };
2170
+ func.runtime.render.clone_runtime_options = function (value) {
2171
+ if (typeof structuredClone === 'function') {
2172
+ try {
2173
+ return structuredClone(value);
2174
+ } catch (_) {}
2175
+ }
2176
+
2177
+ if (Array.isArray(value)) {
2178
+ return value.map(function (item) {
2179
+ return func.runtime.render.clone_runtime_options(item);
2180
+ });
2181
+ }
2182
+
2183
+ if (value && typeof value === 'object') {
2184
+ const cloned = {};
2185
+ const keys = Object.keys(value);
2186
+ for (let index = 0; index < keys.length; index++) {
2187
+ const key = keys[index];
2188
+ cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
2189
+ }
2190
+ return cloned;
2191
+ }
2192
+
2193
+ return value;
2194
+ };
2195
+ func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
2196
+ const options = raw_options || {};
2197
+ let app_computing_mode = options.app_computing_mode || '';
2198
+ let app_render_mode = options.app_render_mode || '';
2199
+ let app_client_activation = options.app_client_activation || '';
2200
+ let ssr_payload = options.ssr_payload || null;
2201
+
2202
+ if (typeof ssr_payload === 'string') {
2203
+ try {
2204
+ ssr_payload = JSON.parse(ssr_payload);
2205
+ } catch (_) {
2206
+ ssr_payload = null;
2207
+ }
2208
+ }
2209
+
2210
+ if (ssr_payload && typeof ssr_payload === 'object') {
2211
+ ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
2212
+ }
2213
+
2214
+ if (!app_computing_mode) {
2215
+ if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
2216
+ app_computing_mode = 'server';
2217
+ } else {
2218
+ app_computing_mode = 'main';
2219
+ }
2220
+ }
2221
+
2222
+ switch (app_computing_mode) {
2223
+ case 'main':
2224
+ app_render_mode = 'csr';
2225
+ app_client_activation = 'none';
2226
+ break;
2227
+
2228
+ case 'worker':
2229
+ app_render_mode = 'csr';
2230
+ app_client_activation = 'none';
2231
+ break;
2232
+
2233
+ default:
2234
+ app_computing_mode = 'server';
2235
+ if (app_render_mode !== 'ssr_full') {
2236
+ app_render_mode = 'ssr_first_page';
2237
+ }
2238
+ app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
2239
+ break;
2240
+ }
2241
+
2242
+ if (ssr_payload && typeof ssr_payload === 'object') {
2243
+ if (!ssr_payload.app_render_mode) {
2244
+ ssr_payload.app_render_mode = app_render_mode;
2245
+ }
2246
+ if (!ssr_payload.app_client_activation) {
2247
+ ssr_payload.app_client_activation = app_client_activation;
2248
+ }
2249
+ if (!ssr_payload.app_computing_mode) {
2250
+ ssr_payload.app_computing_mode = app_computing_mode;
2251
+ }
2252
+ }
2253
+
2254
+ return {
2255
+ app_computing_mode,
2256
+ app_render_mode,
2257
+ app_client_activation,
2258
+ ssr_payload,
2259
+ };
2260
+ };
2261
+ func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
2262
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
2263
+ target.app_computing_mode = normalized.app_computing_mode;
2264
+ target.app_render_mode = normalized.app_render_mode;
2265
+ target.app_client_activation = normalized.app_client_activation;
2266
+ target.ssr_payload = normalized.ssr_payload;
2267
+ return normalized;
2268
+ };
2269
+ func.runtime.render.is_server_render_mode = function (target = {}) {
2270
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2271
+ return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
2272
+ };
2273
+ func.runtime.render.is_takeover_mode = function (target = {}) {
2274
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2275
+ return normalized.app_client_activation === 'takeover';
2276
+ };
2277
+ func.runtime.render.is_hydration_mode = function (target = {}) {
2278
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2279
+ return normalized.app_client_activation === 'hydrate';
2280
+ };
2281
+ func.runtime.render.get_ssr_payload = function (target = {}) {
2282
+ if (target?.opt?.ssr_payload) {
2283
+ return target.opt.ssr_payload;
2284
+ }
2285
+ if (target?.ssr_payload) {
2286
+ return target.ssr_payload;
2287
+ }
2288
+ const win = func.runtime.platform.get_window();
2289
+ return win?.__XUDA_SSR__ || null;
2290
+ };
2291
+ func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
2292
+ const session = SESSION_OBJ?.[SESSION_ID];
2293
+ const payload = func.runtime.render.get_ssr_payload(session);
2294
+ if (!payload || payload._consumed) {
2295
+ return false;
2296
+ }
2297
+ if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
2298
+ return false;
2299
+ }
2300
+ return true;
2301
+ };
2302
+ func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
2303
+ const session = SESSION_OBJ?.[SESSION_ID];
2304
+ const payload = func.runtime.render.get_ssr_payload(session);
2305
+ if (!payload || typeof payload !== 'object') {
2306
+ return false;
2307
+ }
2308
+ payload._consumed = true;
2309
+ return true;
2310
+ };
2101
2311
  func.runtime.render.get_root_data_system = function (SESSION_ID) {
2102
2312
  return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
2103
2313
  };
@@ -2501,10 +2711,10 @@ func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
2501
2711
  await func.utils.load_js_on_demand(normalized_resource.src);
2502
2712
  break;
2503
2713
  case 'css':
2504
- await func.utils.load_js_on_demand(normalized_resource.src);
2714
+ func.runtime.platform.load_css(normalized_resource.src);
2505
2715
  break;
2506
2716
  case 'module':
2507
- func.utils.load_js_on_demand(normalized_resource.src, 'module');
2717
+ await func.utils.load_js_on_demand(normalized_resource.src, 'module');
2508
2718
  break;
2509
2719
  default:
2510
2720
  await func.utils.load_js_on_demand(normalized_resource.src);
@@ -3817,30 +4027,715 @@ func.common.get_data_from_websocket = async function (SESSION_ID, serviceP, data
3817
4027
  // // The .toString(36) method handles the conversion to an alphanumeric representation (0-9, a-z).
3818
4028
  // const base36Hash = bigInt.toString(36);
3819
4029
 
3820
- // // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
3821
- // // For a 64-bit integer, the Base36 representation will be about 13 characters long,
3822
- // // so slicing is a reliable way to get a fixed length.
3823
- // const shortHash = base36Hash.slice(0, 10);
4030
+ // // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
4031
+ // // For a 64-bit integer, the Base36 representation will be about 13 characters long,
4032
+ // // so slicing is a reliable way to get a fixed length.
4033
+ // const shortHash = base36Hash.slice(0, 10);
4034
+
4035
+ // // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
4036
+ // // This ensures the output is always exactly 10 characters long.
4037
+ // return shortHash.padStart(10, '0');
4038
+ // };
4039
+
4040
+ func.common.fastHash = function (inputString) {
4041
+ let hash = 0x811c9dc5; // FNV offset basis
4042
+
4043
+ for (let i = 0; i < inputString.length; i++) {
4044
+ hash ^= inputString.charCodeAt(i);
4045
+ // FNV prime multiplication with 32-bit overflow
4046
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
4047
+ }
4048
+
4049
+ // Convert to base36 and pad to 10 characters
4050
+ return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
4051
+ };
4052
+
4053
+ glb.new_xu_render = false;
4054
+ func.runtime = func.runtime || {};
4055
+ func.runtime.ui = func.runtime.ui || {};
4056
+ func.runtime.render = func.runtime.render || {};
4057
+ func.runtime.widgets = func.runtime.widgets || {};
4058
+
4059
+ // Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
4060
+
4061
+ func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
4062
+ func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
4063
+
4064
+ func.runtime.render.safe_clone_tree_value = function (value) {
4065
+ if (typeof structuredClone === 'function') {
4066
+ try {
4067
+ return structuredClone(value);
4068
+ } catch (_) {
4069
+ // Fall through to the recursive clone below.
4070
+ }
4071
+ }
4072
+
4073
+ if (Array.isArray(value)) {
4074
+ return value.map(function (item) {
4075
+ return func.runtime.render.safe_clone_tree_value(item);
4076
+ });
4077
+ }
4078
+
4079
+ if (value && typeof value === 'object') {
4080
+ const cloned = {};
4081
+ const keys = Object.keys(value);
4082
+ for (let index = 0; index < keys.length; index++) {
4083
+ const key = keys[index];
4084
+ cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
4085
+ }
4086
+ return cloned;
4087
+ }
4088
+
4089
+ return value;
4090
+ };
4091
+ func.runtime.render.sort_tree_debug_value = function (value) {
4092
+ if (Array.isArray(value)) {
4093
+ return value.map(function (item) {
4094
+ return func.runtime.render.sort_tree_debug_value(item);
4095
+ });
4096
+ }
4097
+
4098
+ if (value && typeof value === 'object') {
4099
+ const sorted = {};
4100
+ const keys = Object.keys(value).sort();
4101
+ for (let index = 0; index < keys.length; index++) {
4102
+ const key = keys[index];
4103
+ sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
4104
+ }
4105
+ return sorted;
4106
+ }
4107
+
4108
+ return value;
4109
+ };
4110
+ func.runtime.render.is_tree_node = function (nodeP) {
4111
+ return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
4112
+ };
4113
+ func.runtime.render.get_tree_source_node = function (nodeP) {
4114
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4115
+ return nodeP || null;
4116
+ }
4117
+ return nodeP?.meta?.source_node || null;
4118
+ };
4119
+ func.runtime.render.get_tree_source_snapshot = function (nodeP) {
4120
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4121
+ return func.runtime.render.safe_clone_tree_value(nodeP);
4122
+ }
4123
+ return nodeP?.meta?.source_snapshot || null;
4124
+ };
4125
+ func.runtime.render.get_tree_node_kind = function (nodeP) {
4126
+ const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
4127
+ const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
4128
+
4129
+ if (tag_name === 'xu-widget') return 'widget';
4130
+ if (tag_name === 'xu-single-view') return 'single_view';
4131
+ if (tag_name === 'xu-multi-view') return 'multi_view';
4132
+ if (tag_name === 'xu-panel') return 'panel';
4133
+ if (tag_name === 'xu-teleport') return 'teleport';
4134
+ if (tag_name === 'xurender') return 'placeholder';
4135
+ if (tag_name === '#text' || node_type === 'text') return 'text';
4136
+ if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
4137
+ return 'element';
4138
+ };
4139
+ func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
4140
+ if (nodeP?.id) {
4141
+ return nodeP.id;
4142
+ }
4143
+ if (nodeP?.id_org) {
4144
+ return nodeP.id_org;
4145
+ }
4146
+ const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
4147
+ return `tree-node-${normalized_path}`;
4148
+ };
4149
+ func.runtime.render.get_tree_controls = function (attributes) {
4150
+ const attrs = attributes || {};
4151
+ const get_first_defined = function (keys) {
4152
+ for (let index = 0; index < keys.length; index++) {
4153
+ const key = keys[index];
4154
+ if (Object.prototype.hasOwnProperty.call(attrs, key)) {
4155
+ return attrs[key];
4156
+ }
4157
+ }
4158
+ return null;
4159
+ };
4160
+ return {
4161
+ xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
4162
+ xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
4163
+ xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
4164
+ };
4165
+ };
4166
+ func.runtime.render.get_tree_node_capabilities = async function (options) {
4167
+ const attributes = options?.attributes || {};
4168
+ const plugin_name = attributes['xu-widget'];
4169
+ if (!plugin_name) {
4170
+ return null;
4171
+ }
4172
+
4173
+ const cache = func.runtime.render._tree_widget_capability_cache;
4174
+ if (cache[plugin_name]) {
4175
+ return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
4176
+ }
4177
+
4178
+ let capabilities = {
4179
+ browser: true,
4180
+ headless: false,
4181
+ };
4182
+
4183
+ try {
4184
+ if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
4185
+ const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
4186
+ const definition = await func.runtime.widgets.get_definition(widget_context);
4187
+ capabilities = func.runtime.widgets.normalize_capabilities(definition);
4188
+ }
4189
+ } catch (_) {
4190
+ // Keep the safe browser-only default when the widget definition is unavailable.
4191
+ }
4192
+
4193
+ cache[plugin_name] = capabilities;
4194
+ return func.runtime.render.safe_clone_tree_value(capabilities);
4195
+ };
4196
+ func.runtime.render.ensure_tree_node = async function (options) {
4197
+ if (!options?.nodeP) {
4198
+ return null;
4199
+ }
4200
+ if (func.runtime.render.is_tree_node(options.nodeP)) {
4201
+ return options.nodeP;
4202
+ }
4203
+ return await func.runtime.render.build_tree(options);
4204
+ };
4205
+ func.runtime.render.build_tree = async function (options) {
4206
+ if (Array.isArray(options?.nodeP)) {
4207
+ return await func.runtime.render.build_tree_list({
4208
+ ...options,
4209
+ nodesP: options.nodeP,
4210
+ });
4211
+ }
4212
+
4213
+ const nodeP = options?.nodeP;
4214
+ if (!nodeP) {
4215
+ return null;
4216
+ }
4217
+ if (func.runtime.render.is_tree_node(nodeP)) {
4218
+ return nodeP;
4219
+ }
4220
+
4221
+ const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
4222
+ const tree_path = pathP.length ? pathP.slice() : [0];
4223
+ const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
4224
+ if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined') {
4225
+ attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
4226
+ }
4227
+
4228
+ const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
4229
+ SESSION_ID: options?.SESSION_ID,
4230
+ paramsP: options?.paramsP,
4231
+ attributes,
4232
+ });
4233
+ const children = [];
4234
+ const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
4235
+ const parent_tree_id = tree_path.join('.');
4236
+
4237
+ for (let index = 0; index < child_nodes.length; index++) {
4238
+ const child_tree = await func.runtime.render.build_tree({
4239
+ ...options,
4240
+ nodeP: child_nodes[index],
4241
+ pathP: tree_path.concat(index),
4242
+ parent_tree_id: parent_tree_id,
4243
+ keyP: index,
4244
+ parent_nodeP: nodeP,
4245
+ });
4246
+ if (child_tree) {
4247
+ children.push(child_tree);
4248
+ }
4249
+ }
4250
+
4251
+ const tree = {
4252
+ contract: func.runtime.render.TREE_CONTRACT_VERSION,
4253
+ id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
4254
+ xu_tree_id: `tree.${tree_path.join('.')}`,
4255
+ kind: func.runtime.render.get_tree_node_kind(nodeP),
4256
+ tagName: nodeP.tagName || null,
4257
+ attributes,
4258
+ text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
4259
+ content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
4260
+ children,
4261
+ meta: {
4262
+ tree_id: tree_path.join('.'),
4263
+ path: tree_path,
4264
+ parent_tree_id: options?.parent_tree_id || null,
4265
+ key: typeof options?.keyP === 'undefined' ? null : options.keyP,
4266
+ recordid: nodeP?.recordid || null,
4267
+ dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
4268
+ iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
4269
+ controls: func.runtime.render.get_tree_controls(attributes),
4270
+ capabilities: widget_capabilities,
4271
+ widget: attributes['xu-widget']
4272
+ ? {
4273
+ plugin_name: attributes['xu-widget'],
4274
+ method: attributes['xu-method'] || '_default',
4275
+ capabilities: widget_capabilities,
4276
+ }
4277
+ : null,
4278
+ source_node_id: nodeP?.id || nodeP?.id_org || null,
4279
+ source_node: nodeP,
4280
+ source_snapshot: func.runtime.ui?.get_node_snapshot
4281
+ ? func.runtime.ui.get_node_snapshot(nodeP)
4282
+ : func.runtime.render.safe_clone_tree_value(nodeP),
4283
+ },
4284
+ };
4285
+
4286
+ return tree;
4287
+ };
4288
+ func.runtime.render.build_tree_list = async function (options) {
4289
+ const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
4290
+ const trees = [];
4291
+
4292
+ for (let index = 0; index < nodes.length; index++) {
4293
+ const tree = await func.runtime.render.build_tree({
4294
+ ...options,
4295
+ nodeP: nodes[index],
4296
+ pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
4297
+ keyP: index,
4298
+ });
4299
+ if (tree) {
4300
+ trees.push(tree);
4301
+ }
4302
+ }
4303
+
4304
+ return trees;
4305
+ };
4306
+ func.runtime.render.sanitize_tree_for_debug = function (treeP) {
4307
+ if (Array.isArray(treeP)) {
4308
+ return treeP.map(function (child) {
4309
+ return func.runtime.render.sanitize_tree_for_debug(child);
4310
+ });
4311
+ }
4312
+
4313
+ if (!func.runtime.render.is_tree_node(treeP)) {
4314
+ return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
4315
+ }
4316
+
4317
+ return {
4318
+ contract: treeP.contract,
4319
+ id: treeP.id,
4320
+ xu_tree_id: treeP.xu_tree_id || null,
4321
+ kind: treeP.kind,
4322
+ tagName: treeP.tagName,
4323
+ attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
4324
+ text: treeP.text,
4325
+ content: treeP.content,
4326
+ children: treeP.children.map(function (child) {
4327
+ return func.runtime.render.sanitize_tree_for_debug(child);
4328
+ }),
4329
+ meta: {
4330
+ tree_id: treeP.meta?.tree_id || null,
4331
+ path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
4332
+ parent_tree_id: treeP.meta?.parent_tree_id || null,
4333
+ key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
4334
+ recordid: treeP.meta?.recordid || null,
4335
+ dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
4336
+ iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
4337
+ controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
4338
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
4339
+ widget: treeP.meta?.widget
4340
+ ? {
4341
+ plugin_name: treeP.meta.widget.plugin_name,
4342
+ method: treeP.meta.widget.method,
4343
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
4344
+ }
4345
+ : null,
4346
+ source_node_id: treeP.meta?.source_node_id || null,
4347
+ },
4348
+ };
4349
+ };
4350
+ func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
4351
+ return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
4352
+ };
4353
+ func.runtime = func.runtime || {};
4354
+ func.runtime.ui = func.runtime.ui || {};
4355
+ func.runtime.render = func.runtime.render || {};
4356
+ func.runtime.widgets = func.runtime.widgets || {};
4357
+
4358
+ // Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
4359
+
4360
+ func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
4361
+ area: true,
4362
+ base: true,
4363
+ br: true,
4364
+ col: true,
4365
+ embed: true,
4366
+ hr: true,
4367
+ img: true,
4368
+ input: true,
4369
+ link: true,
4370
+ meta: true,
4371
+ param: true,
4372
+ source: true,
4373
+ track: true,
4374
+ wbr: true,
4375
+ };
4376
+ func.runtime.render.escape_html = function (value) {
4377
+ return `${value ?? ''}`
4378
+ .replaceAll('&', '&amp;')
4379
+ .replaceAll('<', '&lt;')
4380
+ .replaceAll('>', '&gt;')
4381
+ .replaceAll('"', '&quot;')
4382
+ .replaceAll("'", '&#039;');
4383
+ };
4384
+ func.runtime.render.escape_html_attribute = function (value) {
4385
+ return func.runtime.render.escape_html(value);
4386
+ };
4387
+ func.runtime.render.is_html_void_tag = function (tag_name) {
4388
+ return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
4389
+ };
4390
+ func.runtime.render.is_falsey_render_value = function (value) {
4391
+ if (value === false || value === null || typeof value === 'undefined') {
4392
+ return true;
4393
+ }
4394
+ if (typeof value === 'number') {
4395
+ return value === 0;
4396
+ }
4397
+ if (typeof value === 'string') {
4398
+ const normalized = value.trim().toLowerCase();
4399
+ return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
4400
+ }
4401
+ return false;
4402
+ };
4403
+ func.runtime.render.should_render_tree_node = function (treeP) {
4404
+ const controls = treeP?.meta?.controls || {};
4405
+ if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
4406
+ return false;
4407
+ }
4408
+ if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
4409
+ return false;
4410
+ }
4411
+ return true;
4412
+ };
4413
+ func.runtime.render.is_tree_control_attribute = function (key) {
4414
+ if (!key) {
4415
+ return false;
4416
+ }
4417
+ return (
4418
+ key.startsWith('xu-exp:') ||
4419
+ key === 'xu-widget' ||
4420
+ key === 'xu-method' ||
4421
+ key === 'xu-for' ||
4422
+ key === 'xu-for-key' ||
4423
+ key === 'xu-for-val' ||
4424
+ key === 'xu-if' ||
4425
+ key === 'xu-render' ||
4426
+ key === 'xu-bind' ||
4427
+ key === 'xu-content' ||
4428
+ key === 'xu-text' ||
4429
+ key === 'xu-html' ||
4430
+ key === 'xu-show' ||
4431
+ key === 'xu-panel-program' ||
4432
+ key === 'xu-teleport'
4433
+ );
4434
+ };
4435
+ func.runtime.render.get_string_renderer_tag_name = function (treeP) {
4436
+ switch (treeP?.kind) {
4437
+ case 'widget':
4438
+ case 'single_view':
4439
+ case 'multi_view':
4440
+ case 'panel':
4441
+ case 'teleport':
4442
+ return 'div';
4443
+ case 'placeholder':
4444
+ return null;
4445
+ case 'text':
4446
+ return null;
4447
+ default:
4448
+ return treeP?.tagName || 'div';
4449
+ }
4450
+ };
4451
+ func.runtime.render.get_tree_terminal_content = function (treeP) {
4452
+ const attributes = treeP?.attributes || {};
4453
+ if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
4454
+ return {
4455
+ value: `${attributes['xu-html']}`,
4456
+ mode: 'html',
4457
+ };
4458
+ }
4459
+ if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
4460
+ return {
4461
+ value: `${attributes['xu-content']}`,
4462
+ mode: 'html',
4463
+ };
4464
+ }
4465
+ if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
4466
+ return {
4467
+ value: `${attributes['xu-text']}`,
4468
+ mode: 'text',
4469
+ };
4470
+ }
4471
+ if (treeP?.kind === 'text') {
4472
+ return {
4473
+ value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
4474
+ mode: 'text',
4475
+ };
4476
+ }
4477
+ return null;
4478
+ };
4479
+ func.runtime.render.render_tree_terminal_content = function (treeP) {
4480
+ const terminal = func.runtime.render.get_tree_terminal_content(treeP);
4481
+ if (!terminal) {
4482
+ return null;
4483
+ }
4484
+ if (terminal.mode === 'html') {
4485
+ return terminal.value;
4486
+ }
4487
+ return func.runtime.render.escape_html(terminal.value);
4488
+ };
4489
+ func.runtime.render.get_widget_fallback_markup = function (treeP) {
4490
+ const widget_meta = treeP?.meta?.widget || {};
4491
+ const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
4492
+ return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
4493
+ };
4494
+ func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
4495
+ const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
4496
+ const attr_pairs = [];
4497
+ const keys = Object.keys(attributes);
4498
+
4499
+ for (let index = 0; index < keys.length; index++) {
4500
+ const key = keys[index];
4501
+ if (func.runtime.render.is_tree_control_attribute(key)) {
4502
+ continue;
4503
+ }
4504
+ const value = attributes[key];
4505
+ if (value === false || value === null || typeof value === 'undefined') {
4506
+ continue;
4507
+ }
4508
+ if (value === true) {
4509
+ attr_pairs.push(key);
4510
+ continue;
4511
+ }
4512
+ const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
4513
+ attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
4514
+ }
4515
+
4516
+ attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
4517
+ attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
4518
+ attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
4519
+
4520
+ if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
4521
+ attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
4522
+ attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
4523
+ attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
4524
+ }
4525
+
4526
+ if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
4527
+ attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
4528
+ }
4529
+
4530
+ if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
4531
+ attr_pairs.push('data-xuda-xu-for="pending"');
4532
+ }
4533
+
4534
+ return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
4535
+ };
4536
+ func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
4537
+ if (!Array.isArray(treeP?.children) || !treeP.children.length) {
4538
+ return '';
4539
+ }
4540
+ let html = '';
4541
+ for (let index = 0; index < treeP.children.length; index++) {
4542
+ html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
4543
+ ...renderer_context,
4544
+ parent_tree: treeP,
4545
+ });
4546
+ }
4547
+ return html;
4548
+ };
4549
+ func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
4550
+ if (!treeP) {
4551
+ return '';
4552
+ }
4553
+ if (Array.isArray(treeP)) {
4554
+ let html = '';
4555
+ for (let index = 0; index < treeP.length; index++) {
4556
+ html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
4557
+ }
4558
+ return html;
4559
+ }
4560
+
4561
+ const ensured_tree = await func.runtime.render.ensure_tree_node({
4562
+ SESSION_ID: renderer_context?.SESSION_ID,
4563
+ nodeP: treeP,
4564
+ paramsP: renderer_context?.paramsP,
4565
+ parent_infoP: renderer_context?.parent_infoP,
4566
+ keyP: renderer_context?.keyP,
4567
+ parent_nodeP: renderer_context?.parent_nodeP,
4568
+ });
4569
+
4570
+ if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
4571
+ return '';
4572
+ }
4573
+
4574
+ if (ensured_tree.kind === 'placeholder') {
4575
+ if (renderer_context?.include_placeholders) {
4576
+ return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
4577
+ }
4578
+ return '';
4579
+ }
4580
+
4581
+ if (ensured_tree.kind === 'text') {
4582
+ return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
4583
+ }
4584
+
4585
+ const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
4586
+ if (!tag_name || tag_name.toLowerCase() === 'script') {
4587
+ return '';
4588
+ }
4589
+
4590
+ const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
4591
+ const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
4592
+ let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
4593
+
4594
+ if (ensured_tree.kind === 'widget' && !children_html) {
4595
+ children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
4596
+ }
4597
+
4598
+ if (func.runtime.render.is_html_void_tag(tag_name)) {
4599
+ return `<${tag_name}${attributes}>`;
4600
+ }
4601
+
4602
+ return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
4603
+ };
4604
+ func.runtime.render.render_to_string = async function (options = {}) {
4605
+ const treeP = await func.runtime.render.ensure_tree_node({
4606
+ SESSION_ID: options.SESSION_ID,
4607
+ nodeP: options.treeP || options.nodeP,
4608
+ paramsP: options.paramsP,
4609
+ parent_infoP: options.parent_infoP,
4610
+ keyP: options.keyP,
4611
+ parent_nodeP: options.parent_nodeP,
4612
+ });
4613
+
4614
+ return await func.runtime.render.render_tree_to_string(treeP, options);
4615
+ };
4616
+ func.runtime.render.get_server_render_mode = function (options = {}) {
4617
+ const normalized = func.runtime.render.normalize_runtime_bootstrap({
4618
+ app_computing_mode: options.app_computing_mode,
4619
+ app_render_mode: options.app_render_mode,
4620
+ app_client_activation: options.app_client_activation,
4621
+ });
4622
+
4623
+ return normalized;
4624
+ };
4625
+ func.runtime.render.build_server_render_params = async function (options = {}) {
4626
+ const SESSION_ID = options.SESSION_ID;
4627
+ const prog_id = options.prog_id;
4628
+ const dsSessionP = options.dsSessionP;
4629
+ const _session = SESSION_OBJ?.[SESSION_ID] || {};
4630
+ const _ds = _session?.DS_GLB?.[dsSessionP] || {};
4631
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4632
+
4633
+ if (!viewDoc?.properties) {
4634
+ throw new Error(`view document not found for ${prog_id}`);
4635
+ }
3824
4636
 
3825
- // // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
3826
- // // This ensures the output is always exactly 10 characters long.
3827
- // return shortHash.padStart(10, '0');
3828
- // };
4637
+ const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
4638
+ const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
4639
+ const paramsP = {
4640
+ ...base_params,
4641
+ prog_id,
4642
+ sourceScreenP: null,
4643
+ $callingContainerP: null,
4644
+ triggerIdP: null,
4645
+ callingDataSource_objP: _ds,
4646
+ rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
4647
+ renderType: viewDoc.properties?.renderType,
4648
+ parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
4649
+ source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
4650
+ is_panelP: false,
4651
+ screen_type: options.screen_type || base_params.screen_type || 'render_string',
4652
+ screenInfo: viewDoc,
4653
+ call_screen_propertiesP: base_params.call_screen_propertiesP,
4654
+ parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
4655
+ parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
4656
+ dsSessionP,
4657
+ screenId,
4658
+ containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
4659
+ };
3829
4660
 
3830
- func.common.fastHash = function (inputString) {
3831
- let hash = 0x811c9dc5; // FNV offset basis
4661
+ if (_ds) {
4662
+ _ds.screen_params = paramsP;
4663
+ }
3832
4664
 
3833
- for (let i = 0; i < inputString.length; i++) {
3834
- hash ^= inputString.charCodeAt(i);
3835
- // FNV prime multiplication with 32-bit overflow
3836
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
4665
+ return paramsP;
4666
+ };
4667
+ func.runtime.render.build_prog_tree = async function (options = {}) {
4668
+ const SESSION_ID = options.SESSION_ID;
4669
+ const prog_id = options.prog_id;
4670
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4671
+
4672
+ if (!viewDoc?.progUi?.length) {
4673
+ throw new Error(`progUi not found for ${prog_id}`);
3837
4674
  }
3838
4675
 
3839
- // Convert to base36 and pad to 10 characters
3840
- return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
4676
+ const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
4677
+ ...options,
4678
+ SESSION_ID,
4679
+ prog_id,
4680
+ viewDoc,
4681
+ }));
4682
+ const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
4683
+ const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
4684
+ const tree = await func.runtime.render.build_tree({
4685
+ SESSION_ID,
4686
+ nodeP: root_node,
4687
+ paramsP,
4688
+ });
4689
+
4690
+ return {
4691
+ tree,
4692
+ paramsP,
4693
+ viewDoc,
4694
+ };
3841
4695
  };
4696
+ func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
4697
+ const runtime_profile = func.runtime.render.get_server_render_mode(options);
4698
+ return {
4699
+ contract: 'xuda.ssr.v1',
4700
+ prog_id: options.prog_id,
4701
+ screenId: render_program.paramsP.screenId,
4702
+ containerId: render_program.paramsP.containerIdP,
4703
+ app_computing_mode: runtime_profile.app_computing_mode,
4704
+ app_render_mode: runtime_profile.app_render_mode,
4705
+ app_client_activation: runtime_profile.app_client_activation,
4706
+ tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
4707
+ };
4708
+ };
4709
+ func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
4710
+ const payload = func.runtime.render.build_ssr_payload(render_program, options);
4711
+ const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
4712
+ const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
4713
+ const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
4714
+
4715
+ 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>`;
4716
+ };
4717
+ func.runtime.render.render_prog_to_string = async function (options = {}) {
4718
+ const render_program = await func.runtime.render.build_prog_tree(options);
4719
+ const html = await func.runtime.render.render_to_string({
4720
+ ...options,
4721
+ SESSION_ID: options.SESSION_ID,
4722
+ treeP: render_program.tree,
4723
+ paramsP: render_program.paramsP,
4724
+ });
4725
+ const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
4726
+ const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
3842
4727
 
3843
- glb.new_xu_render = false;
4728
+ return {
4729
+ prog_id: options.prog_id,
4730
+ dsSessionP: render_program.paramsP.dsSessionP,
4731
+ screenId: render_program.paramsP.screenId,
4732
+ html,
4733
+ screen_html,
4734
+ tree_json: func.runtime.render.serialize_tree(render_program.tree),
4735
+ paramsP: render_program.paramsP,
4736
+ ssr_payload,
4737
+ };
4738
+ };
3844
4739
  func.runtime = func.runtime || {};
3845
4740
  func.runtime.platform = func.runtime.platform || {};
3846
4741
 
@@ -9230,6 +10125,15 @@ func.runtime.ui.ensure_embed_container = function (SESSION_ID) {
9230
10125
  const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
9231
10126
  let $embed_container = func.runtime.ui.find_by_selector($root_element, `#embed_${SESSION_ID}`, true);
9232
10127
 
10128
+ if (!$embed_container.length) {
10129
+ const $ssr_embed_container = func.runtime.ui.find_by_selector($root_element, `[data-xuda-ssr-embed="true"]`, true);
10130
+ const ssr_embed_node = func.runtime.ui.get_first_node($ssr_embed_container);
10131
+ if (ssr_embed_node) {
10132
+ ssr_embed_node.id = 'embed_' + SESSION_ID;
10133
+ $embed_container = func.runtime.ui._wrap_matches([ssr_embed_node]);
10134
+ }
10135
+ }
10136
+
9233
10137
  if (!$embed_container.length) {
9234
10138
  const embed_node = document.createElement('div');
9235
10139
  embed_node.id = 'embed_' + SESSION_ID;
@@ -9265,12 +10169,44 @@ func.runtime.ui.get_root_tag_name = function () {
9265
10169
  }
9266
10170
  return root_tag_name;
9267
10171
  };
10172
+ func.runtime.ui.find_ssr_screen_host = function ($container, screenId, containerId) {
10173
+ const container_node = func.runtime.ui.get_first_node($container);
10174
+ if (!container_node || !screenId) {
10175
+ return null;
10176
+ }
10177
+
10178
+ const dialog_node =
10179
+ container_node.querySelector?.(`#${CSS?.escape ? CSS.escape(screenId) : screenId}`) ||
10180
+ container_node.querySelector?.(`[data-xuda-ssr-screen-id="${screenId}"]`);
10181
+ if (!dialog_node) {
10182
+ return null;
10183
+ }
10184
+
10185
+ const root_frame_node =
10186
+ (containerId ? dialog_node.querySelector?.(`#${CSS?.escape ? CSS.escape(containerId) : containerId}`) : null) ||
10187
+ dialog_node.querySelector?.('[data-xuda-ssr-root-frame="true"]');
10188
+ if (!root_frame_node) {
10189
+ return null;
10190
+ }
10191
+
10192
+ return {
10193
+ $dialogDiv: func.runtime.ui._wrap_matches([dialog_node]),
10194
+ $rootFrame: func.runtime.ui._wrap_matches([root_frame_node]),
10195
+ reused_ssr_host: true,
10196
+ };
10197
+ };
9268
10198
  func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params, $callingContainerP, screenId) {
9269
10199
  var $dialogDiv;
9270
10200
  var $rootFrame;
10201
+ let reused_ssr_host = false;
9271
10202
 
9272
10203
  switch (screen_type) {
9273
10204
  case 'embed': {
10205
+ const ssr_host = func.runtime.ui.find_ssr_screen_host($callingContainerP, screenId, params?.containerIdP);
10206
+ if (ssr_host) {
10207
+ return ssr_host;
10208
+ }
10209
+
9274
10210
  const dialogNode = document.createElement('div');
9275
10211
  dialogNode.id = screenId;
9276
10212
  dialogNode.setAttribute('ui_engine', UI_FRAMEWORK_INSTALLED);
@@ -9320,6 +10256,7 @@ func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params,
9320
10256
  return {
9321
10257
  $dialogDiv,
9322
10258
  $rootFrame,
10259
+ reused_ssr_host,
9323
10260
  };
9324
10261
  };
9325
10262
  func.runtime.ui.find_xu_ui_in_root = function (SESSION_ID, xu_ui_id) {
@@ -9884,6 +10821,11 @@ func.runtime.ui.build_container_xu_data = function (options) {
9884
10821
  func.runtime.ui.apply_container_meta = function ($div, options) {
9885
10822
  const div_node = func.runtime.ui.get_first_node($div);
9886
10823
  func.runtime.ui.set_attr(div_node, 'xu-ui-id', options.ui_id);
10824
+ func.runtime.ui.set_attr(div_node, 'data-xuda-kind', options.treeP?.kind || options.nodeP?.tagName || 'element');
10825
+ func.runtime.ui.set_attr(div_node, 'data-xuda-node-id', options.nodeP?.id || options.nodeP?.id_org || '');
10826
+ if (options.treeP?.meta?.tree_id !== null && typeof options.treeP?.meta?.tree_id !== 'undefined') {
10827
+ func.runtime.ui.set_attr(div_node, 'data-xuda-tree-id', options.treeP.meta.tree_id);
10828
+ }
9887
10829
  const xuData = func.runtime.ui.build_container_xu_data(options);
9888
10830
  if (options.parent_infoP?.iterate_info) {
9889
10831
  xuData.iterate_info = options.parent_infoP.iterate_info;
@@ -9978,6 +10920,38 @@ func.runtime.ui.create_container_element = function (div_typeP, attr_str, prop,
9978
10920
  }
9979
10921
  return func.runtime.ui.create_element(div, attr_str);
9980
10922
  };
10923
+ func.runtime.ui.find_hydration_candidate = function (options) {
10924
+ if (!func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID])) {
10925
+ return null;
10926
+ }
10927
+ if (!func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
10928
+ return null;
10929
+ }
10930
+ if (options.is_placeholder || !options.treeP?.meta?.tree_id) {
10931
+ return null;
10932
+ }
10933
+
10934
+ const append_node = func.runtime.ui.get_first_node(options.$appendTo || options.$container);
10935
+ if (!append_node) {
10936
+ return null;
10937
+ }
10938
+
10939
+ const children = func.runtime.ui.get_children(append_node);
10940
+ for (let index = 0; index < children.length; index++) {
10941
+ const child = children[index];
10942
+ if (child?.__xuda_hydration_claimed) {
10943
+ continue;
10944
+ }
10945
+ if (func.runtime.ui.get_attr(child, 'data-xuda-tree-id') !== `${options.treeP.meta.tree_id}`) {
10946
+ continue;
10947
+ }
10948
+ child.__xuda_hydration_claimed = true;
10949
+ func.runtime.ui.set_attr(child, 'data-xuda-client-activation', 'hydrate');
10950
+ return child;
10951
+ }
10952
+
10953
+ return null;
10954
+ };
9981
10955
  func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
9982
10956
  const nodeId = nodeP.xu_tree_id || nodeP.id;
9983
10957
  const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
@@ -10030,7 +11004,11 @@ func.runtime.ui.create_container = async function (options) {
10030
11004
  try {
10031
11005
  const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
10032
11006
  const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
10033
- const $div = func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
11007
+ const hydration_candidate = func.runtime.ui.find_hydration_candidate({
11008
+ ...options,
11009
+ $appendTo,
11010
+ });
11011
+ const $div = hydration_candidate || func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
10034
11012
  const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
10035
11013
  container_xu_data,
10036
11014
  currentRecordId,
@@ -10056,9 +11034,10 @@ func.runtime.ui.create_container = async function (options) {
10056
11034
  parent_infoP: options.parent_infoP,
10057
11035
  is_placeholder: options.is_placeholder,
10058
11036
  classP: options.classP,
11037
+ treeP: options.treeP,
10059
11038
  });
10060
11039
 
10061
- if (options.div_typeP !== 'svg') {
11040
+ if (!hydration_candidate && options.div_typeP !== 'svg') {
10062
11041
  func.runtime.ui.append_to($div, $appendTo);
10063
11042
  }
10064
11043
  return $div;
@@ -12739,9 +13718,10 @@ func.runtime.ui.init_screen = async function (options) {
12739
13718
 
12740
13719
  const _session = SESSION_OBJ[SESSION_ID];
12741
13720
  const screenInfo = structuredClone(screen_ret);
13721
+ const ssr_payload = func.runtime.render.should_use_ssr_payload(SESSION_ID, { prog_id }) ? func.runtime.render.get_ssr_payload(_session) : null;
12742
13722
 
12743
13723
  const screen_type = source_functionP?.split('_')?.[1];
12744
- const screenId = (glb.screen_num++).toString();
13724
+ const screenId = ssr_payload?.screenId || (glb.screen_num++).toString();
12745
13725
 
12746
13726
  if (SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
12747
13727
  const wait_for_SCREEN_BLOCKER_release = function () {
@@ -12783,6 +13763,8 @@ func.runtime.ui.init_screen = async function (options) {
12783
13763
  call_screen_propertiesP,
12784
13764
  parentDataSourceNoP: _session.DS_GLB?.[callingDataSource_objP?.dsSession]?.dsSession || callingDataSource_objP?.parentDataSourceNo || 0,
12785
13765
  parameters_raw_obj,
13766
+ containerIdP: ssr_payload?.containerId || null,
13767
+ ssr_payload,
12786
13768
  };
12787
13769
 
12788
13770
  const screen_host = func.runtime.ui.create_screen_host(SESSION_ID, screen_type, params, $callingContainerP, screenId);
@@ -12815,6 +13797,10 @@ func.runtime.ui.init_screen = async function (options) {
12815
13797
  func.runtime.ui.set_style($rootFrame, 'display', 'contents');
12816
13798
  }
12817
13799
 
13800
+ if (screen_host.reused_ssr_host && func.runtime.render.is_takeover_mode(_session)) {
13801
+ func.runtime.ui.empty($rootFrame);
13802
+ }
13803
+
12818
13804
  if (!is_panelP) func.UI.utils.indicator.screen.busy();
12819
13805
 
12820
13806
  const ret = await func.datasource.create(
@@ -12856,7 +13842,24 @@ func.runtime.ui.init_screen = async function (options) {
12856
13842
  }
12857
13843
  let node = structuredClone(viewDoc.progUi);
12858
13844
  if (!node.length) return console.warn('ui node empty');
12859
- 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);
13845
+ const root_tree = await func.runtime.render.build_tree({
13846
+ SESSION_ID,
13847
+ nodeP: node[0],
13848
+ paramsP: params,
13849
+ });
13850
+ const ret_render_$container = await func.runtime.render.render_tree(root_tree, {
13851
+ SESSION_ID,
13852
+ $container: $rootFrame,
13853
+ parent_infoP: null,
13854
+ paramsP: params,
13855
+ jobNoP,
13856
+ is_skeleton: null,
13857
+ keyP: null,
13858
+ refreshed_ds: null,
13859
+ parent_nodeP: null,
13860
+ check_existP: null,
13861
+ $root_container: $rootFrame,
13862
+ });
12860
13863
 
12861
13864
  if (!is_panelP) func.UI.utils.indicator.screen.normal();
12862
13865
 
@@ -13281,6 +14284,7 @@ func.runtime.ui.render_single_view_node = async function (options) {
13281
14284
  parent_infoP: options.parent_infoP,
13282
14285
  jobNoP: options.jobNoP,
13283
14286
  keyP: options.keyP,
14287
+ treeP: options.treeP,
13284
14288
  parent_nodeP: options.parent_nodeP,
13285
14289
  prop: options.prop,
13286
14290
  div_typeP: 'div',
@@ -13409,6 +14413,7 @@ func.runtime.ui.render_panel_node = async function (options) {
13409
14413
  parent_infoP: options.parent_infoP,
13410
14414
  jobNoP: options.jobNoP,
13411
14415
  keyP: options.keyP,
14416
+ treeP: options.treeP,
13412
14417
  parent_nodeP: options.parent_nodeP,
13413
14418
  prop: options.prop,
13414
14419
  $appendToP: $wrapper,
@@ -13738,6 +14743,14 @@ func.runtime.ui.screen_loading_done = async function (options) {
13738
14743
  });
13739
14744
 
13740
14745
  const _session = SESSION_OBJ[options.SESSION_ID];
14746
+ if (func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
14747
+ const root_node = func.runtime.ui.get_root_node(options.SESSION_ID);
14748
+ if (root_node) {
14749
+ func.runtime.ui.set_attr(root_node, 'data-xuda-client-activation', _session.opt.app_client_activation || 'none');
14750
+ func.runtime.ui.set_attr(root_node, 'data-xuda-ssr-status', _session.opt.app_client_activation === 'hydrate' ? 'hydrated' : 'taken-over');
14751
+ }
14752
+ func.runtime.render.mark_ssr_payload_consumed(options.SESSION_ID);
14753
+ }
13741
14754
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
13742
14755
  func.UI.utils.screen_blocker(false, options.paramsP.prog_id + (options.paramsP.sourceScreenP ? '_' + options.paramsP.sourceScreenP : ''));
13743
14756
  if (_session.prog_id === options.paramsP.prog_id) {
@@ -15580,6 +16593,9 @@ func.runtime.render.get_screen_context = function (SESSION_ID, $container, param
15580
16593
  };
15581
16594
  func.runtime.render.get_node_attributes = function (nodeP) {
15582
16595
  try {
16596
+ if (func.runtime.render.is_tree_node?.(nodeP)) {
16597
+ return nodeP.attributes;
16598
+ }
15583
16599
  return nodeP?.attributes;
15584
16600
  } catch (error) {
15585
16601
  return undefined;
@@ -15697,6 +16713,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15697
16713
  parent_infoP: options.parent_infoP,
15698
16714
  jobNoP: options.jobNoP,
15699
16715
  keyP: options.keyP,
16716
+ treeP: options.treeP,
15700
16717
  parent_nodeP: options.parent_nodeP,
15701
16718
  prop: options.prop,
15702
16719
  div_typeP: options.element,
@@ -15719,6 +16736,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15719
16736
  parent_infoP: options.parent_infoP,
15720
16737
  jobNoP: options.jobNoP,
15721
16738
  keyP: options.keyP,
16739
+ treeP: options.treeP,
15722
16740
  parent_nodeP: options.parent_nodeP,
15723
16741
  prop: options.prop,
15724
16742
  div_typeP: options.element,
@@ -16575,11 +17593,16 @@ func.runtime.widgets.render_node = async function (options) {
16575
17593
  parent_infoP: options.parent_infoP,
16576
17594
  jobNoP: options.jobNoP,
16577
17595
  keyP: options.keyP,
17596
+ treeP: options.treeP,
16578
17597
  parent_nodeP: options.parent_nodeP,
16579
17598
  prop: options.prop,
16580
17599
  classP: 'widget_wrapper',
16581
17600
  });
16582
17601
 
17602
+ if (func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID]) && func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
17603
+ func.runtime.ui.empty($div);
17604
+ }
17605
+
16583
17606
  const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, options.prop);
16584
17607
  const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
16585
17608
  const report_error = function (descP, warn) {
@@ -16682,9 +17705,306 @@ func.runtime.widgets = func.runtime.widgets || {};
16682
17705
 
16683
17706
  // Browser-only special node renderers live here so the core render tree can stay focused.
16684
17707
 
17708
+ const normalize_runtime_tag_name = function (tag_name) {
17709
+ return `${tag_name || ''}`.trim().toLowerCase();
17710
+ };
17711
+
17712
+ const get_runtime_node_attributes = function (nodeP) {
17713
+ if (!nodeP?.attributes || typeof nodeP.attributes !== 'object') {
17714
+ return {};
17715
+ }
17716
+
17717
+ return nodeP.attributes;
17718
+ };
17719
+
17720
+ const get_runtime_node_content = function (nodeP) {
17721
+ if (typeof nodeP?.content === 'string') {
17722
+ return nodeP.content;
17723
+ }
17724
+
17725
+ if (typeof nodeP?.text === 'string') {
17726
+ return nodeP.text;
17727
+ }
17728
+
17729
+ if (!Array.isArray(nodeP?.children)) {
17730
+ return '';
17731
+ }
17732
+
17733
+ return nodeP.children
17734
+ .map(function (child) {
17735
+ if (typeof child === 'string') {
17736
+ return child;
17737
+ }
17738
+ if (typeof child?.content === 'string') {
17739
+ return child.content;
17740
+ }
17741
+ if (typeof child?.text === 'string') {
17742
+ return child.text;
17743
+ }
17744
+ return '';
17745
+ })
17746
+ .join('');
17747
+ };
17748
+
17749
+ const get_runtime_asset_key = function (options, tag_name) {
17750
+ const source_node = options.treeP || options.nodeP || {};
17751
+ const parts = [
17752
+ 'xuda-html-asset',
17753
+ options.paramsP?.prog_id || '',
17754
+ tag_name,
17755
+ source_node.id || source_node.id_org || '',
17756
+ typeof options.keyP === 'undefined' || options.keyP === null ? '' : `${options.keyP}`,
17757
+ ].filter(function (part) {
17758
+ return `${part || ''}`.trim() !== '';
17759
+ });
17760
+
17761
+ return parts.join(':');
17762
+ };
17763
+
17764
+ const get_runtime_asset_signature = function (attributes, content) {
17765
+ const normalized_attributes = {};
17766
+ const attr_keys = Object.keys(attributes || {}).sort();
17767
+
17768
+ for (let index = 0; index < attr_keys.length; index++) {
17769
+ const key = attr_keys[index];
17770
+ normalized_attributes[key] = attributes[key];
17771
+ }
17772
+
17773
+ return JSON.stringify({
17774
+ attributes: normalized_attributes,
17775
+ content: `${content || ''}`,
17776
+ });
17777
+ };
17778
+
17779
+ const escape_runtime_asset_selector_value = function (value) {
17780
+ const win = func.runtime.platform.get_window?.();
17781
+ if (win?.CSS?.escape) {
17782
+ return win.CSS.escape(value);
17783
+ }
17784
+
17785
+ return `${value || ''}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
17786
+ };
17787
+
17788
+ const find_runtime_head_asset = function (head, tag_name, asset_key) {
17789
+ if (!head?.querySelector || !asset_key) {
17790
+ return null;
17791
+ }
17792
+
17793
+ return head.querySelector(`${tag_name}[data-xuda-asset-key="${escape_runtime_asset_selector_value(asset_key)}"]`);
17794
+ };
17795
+
17796
+ const find_runtime_head_asset_by_attr = function (head, tag_name, attr_name, attr_value) {
17797
+ if (!head?.querySelectorAll || !attr_name || !attr_value) {
17798
+ return null;
17799
+ }
17800
+
17801
+ const candidates = Array.from(head.querySelectorAll(tag_name));
17802
+ return (
17803
+ candidates.find(function (candidate) {
17804
+ return candidate.getAttribute(attr_name) === attr_value || candidate[attr_name] === attr_value;
17805
+ }) || null
17806
+ );
17807
+ };
17808
+
17809
+ const wait_for_runtime_asset_load = function (node) {
17810
+ if (!node?.addEventListener) {
17811
+ return Promise.resolve(node);
17812
+ }
17813
+
17814
+ if (node.getAttribute?.('data-xuda-loaded') === 'true') {
17815
+ return Promise.resolve(node);
17816
+ }
17817
+
17818
+ if (!node.getAttribute?.('data-xuda-asset-key')) {
17819
+ return Promise.resolve(node);
17820
+ }
17821
+
17822
+ const tag_name = normalize_runtime_tag_name(node.tagName);
17823
+ if (tag_name !== 'script' && !(tag_name === 'link' && normalize_runtime_tag_name(node.getAttribute?.('rel')) === 'stylesheet')) {
17824
+ return Promise.resolve(node);
17825
+ }
17826
+
17827
+ return new Promise(function (resolve) {
17828
+ const done = function () {
17829
+ node.setAttribute?.('data-xuda-loaded', 'true');
17830
+ resolve(node);
17831
+ };
17832
+
17833
+ node.addEventListener('load', done, { once: true });
17834
+ node.addEventListener('error', done, { once: true });
17835
+ });
17836
+ };
17837
+
17838
+ const remove_runtime_head_asset = function (node) {
17839
+ if (node?.parentNode?.removeChild) {
17840
+ node.parentNode.removeChild(node);
17841
+ }
17842
+ };
17843
+
17844
+ const apply_runtime_asset_metadata = function (node, asset_key, signature) {
17845
+ if (!node?.setAttribute) {
17846
+ return node;
17847
+ }
17848
+
17849
+ if (asset_key) {
17850
+ node.setAttribute('data-xuda-asset-key', asset_key);
17851
+ }
17852
+ node.setAttribute('data-xuda-asset-signature', signature);
17853
+ return node;
17854
+ };
17855
+
17856
+ const create_runtime_head_element = function (doc, tag_name, attributes, asset_key, signature) {
17857
+ const node = doc.createElement(tag_name);
17858
+ apply_runtime_asset_metadata(node, asset_key, signature);
17859
+ func.runtime.platform.apply_element_attributes(node, attributes);
17860
+ return node;
17861
+ };
17862
+
17863
+ const upsert_runtime_head_element = async function (options) {
17864
+ const doc = func.runtime.platform.get_document?.();
17865
+ const head = doc?.head;
17866
+ const tag_name = normalize_runtime_tag_name(options.tag_name);
17867
+
17868
+ if (!doc?.createElement || !head?.appendChild || !tag_name) {
17869
+ return null;
17870
+ }
17871
+
17872
+ const asset_key = options.asset_key || '';
17873
+ const signature = options.signature || '';
17874
+ const attributes = options.attributes || {};
17875
+ const content = typeof options.content === 'string' ? options.content : '';
17876
+
17877
+ const existing_by_key = find_runtime_head_asset(head, tag_name, asset_key);
17878
+ if (existing_by_key && existing_by_key.getAttribute('data-xuda-asset-signature') === signature) {
17879
+ return options.await_load ? await wait_for_runtime_asset_load(existing_by_key) : existing_by_key;
17880
+ }
17881
+
17882
+ if (existing_by_key) {
17883
+ remove_runtime_head_asset(existing_by_key);
17884
+ }
17885
+
17886
+ if (options.find_existing_attr?.name && options.find_existing_attr?.value) {
17887
+ const existing_by_attr = find_runtime_head_asset_by_attr(head, tag_name, options.find_existing_attr.name, options.find_existing_attr.value);
17888
+ if (existing_by_attr) {
17889
+ apply_runtime_asset_metadata(existing_by_attr, asset_key, signature);
17890
+ existing_by_attr.setAttribute?.('data-xuda-loaded', 'true');
17891
+ return existing_by_attr;
17892
+ }
17893
+ }
17894
+
17895
+ const node = create_runtime_head_element(doc, tag_name, attributes, asset_key, signature);
17896
+
17897
+ if (tag_name === 'script' && attributes.src && !Object.prototype.hasOwnProperty.call(attributes, 'async')) {
17898
+ const script_type = normalize_runtime_tag_name(attributes.type);
17899
+ if (script_type !== 'module') {
17900
+ node.async = false;
17901
+ }
17902
+ }
17903
+
17904
+ if (tag_name === 'style' || (tag_name === 'script' && !attributes.src)) {
17905
+ node.textContent = content;
17906
+ }
17907
+
17908
+ head.appendChild(node);
17909
+
17910
+ if (tag_name !== 'script' && tag_name !== 'link') {
17911
+ node.setAttribute('data-xuda-loaded', 'true');
17912
+ }
17913
+
17914
+ return options.await_load ? await wait_for_runtime_asset_load(node) : node;
17915
+ };
17916
+
17917
+ const render_runtime_html_asset = async function (options) {
17918
+ if (options.is_skeleton) {
17919
+ return options.$container;
17920
+ }
17921
+
17922
+ const nodeP = options.treeP || options.nodeP || {};
17923
+ const tag_name = normalize_runtime_tag_name(nodeP.tagName);
17924
+ const attributes = { ...get_runtime_node_attributes(nodeP) };
17925
+ const content = get_runtime_node_content(nodeP);
17926
+ const asset_key = get_runtime_asset_key(options, tag_name);
17927
+ const signature = get_runtime_asset_signature(attributes, content);
17928
+
17929
+ switch (tag_name) {
17930
+ case 'title':
17931
+ func.runtime.platform.set_title(content);
17932
+ return options.$container;
17933
+ case 'style':
17934
+ await upsert_runtime_head_element({
17935
+ tag_name,
17936
+ attributes,
17937
+ content,
17938
+ asset_key,
17939
+ signature,
17940
+ });
17941
+ return options.$container;
17942
+ case 'meta':
17943
+ if (!Object.keys(attributes).length) {
17944
+ return options.$container;
17945
+ }
17946
+ await upsert_runtime_head_element({
17947
+ tag_name,
17948
+ attributes,
17949
+ asset_key,
17950
+ signature,
17951
+ });
17952
+ return options.$container;
17953
+ case 'link': {
17954
+ const href = `${attributes.href || ''}`.trim();
17955
+ if (!href) {
17956
+ return options.$container;
17957
+ }
17958
+ await upsert_runtime_head_element({
17959
+ tag_name,
17960
+ attributes,
17961
+ asset_key,
17962
+ signature,
17963
+ find_existing_attr: {
17964
+ name: 'href',
17965
+ value: href,
17966
+ },
17967
+ await_load: normalize_runtime_tag_name(attributes.rel) === 'stylesheet',
17968
+ });
17969
+ return options.$container;
17970
+ }
17971
+ case 'script': {
17972
+ const src = `${attributes.src || ''}`.trim();
17973
+ if (!src && !content.trim()) {
17974
+ return options.$container;
17975
+ }
17976
+ await upsert_runtime_head_element({
17977
+ tag_name,
17978
+ attributes,
17979
+ content,
17980
+ asset_key,
17981
+ signature,
17982
+ find_existing_attr: src
17983
+ ? {
17984
+ name: 'src',
17985
+ value: src,
17986
+ }
17987
+ : null,
17988
+ await_load: !!src,
17989
+ });
17990
+ return options.$container;
17991
+ }
17992
+ default:
17993
+ return null;
17994
+ }
17995
+ };
17996
+
16685
17997
  func.runtime.render.render_special_node = async function (options) {
16686
- if (options.nodeP.content && options.nodeP.attributes) {
16687
- options.nodeP.attributes['xu-content'] = options.nodeP.content;
17998
+ const treeP = options.treeP || null;
17999
+ const nodeP = options.nodeP || func.runtime.render.get_tree_source_node(treeP);
18000
+ const render_tag_name = treeP?.tagName || nodeP?.tagName;
18001
+ const normalized_render_tag_name = normalize_runtime_tag_name(render_tag_name);
18002
+ const is_native_html_asset = ['title', 'style', 'meta', 'link', 'script'].includes(normalized_render_tag_name);
18003
+
18004
+ if (!is_native_html_asset && treeP?.content && nodeP?.attributes) {
18005
+ nodeP.attributes['xu-content'] = treeP.content;
18006
+ } else if (!is_native_html_asset && nodeP?.content && nodeP.attributes) {
18007
+ nodeP.attributes['xu-content'] = nodeP.content;
16688
18008
  }
16689
18009
 
16690
18010
  const renderers = {
@@ -16694,6 +18014,7 @@ func.runtime.render.render_special_node = async function (options) {
16694
18014
  SESSION_ID: options.SESSION_ID,
16695
18015
  $container: options.$container,
16696
18016
  $root_container: options.$root_container,
18017
+ treeP,
16697
18018
  nodeP: options.nodeP,
16698
18019
  parent_infoP: options.parent_infoP,
16699
18020
  paramsP: options.paramsP,
@@ -16710,6 +18031,7 @@ func.runtime.render.render_special_node = async function (options) {
16710
18031
  SESSION_ID: options.SESSION_ID,
16711
18032
  $container: options.$container,
16712
18033
  $root_container: options.$root_container,
18034
+ treeP,
16713
18035
  nodeP: options.nodeP,
16714
18036
  parent_infoP: options.parent_infoP,
16715
18037
  paramsP: options.paramsP,
@@ -16729,6 +18051,7 @@ func.runtime.render.render_special_node = async function (options) {
16729
18051
  SESSION_ID: options.SESSION_ID,
16730
18052
  $container: options.$container,
16731
18053
  $root_container: options.$root_container,
18054
+ treeP,
16732
18055
  nodeP: options.nodeP,
16733
18056
  parent_infoP: options.parent_infoP,
16734
18057
  paramsP: options.paramsP,
@@ -16745,6 +18068,7 @@ func.runtime.render.render_special_node = async function (options) {
16745
18068
  SESSION_ID: options.SESSION_ID,
16746
18069
  $container: options.$container,
16747
18070
  $root_container: options.$root_container,
18071
+ treeP,
16748
18072
  nodeP: options.nodeP,
16749
18073
  parent_infoP: options.parent_infoP,
16750
18074
  paramsP: options.paramsP,
@@ -16756,9 +18080,24 @@ func.runtime.render.render_special_node = async function (options) {
16756
18080
  refreshed_ds: options.refreshed_ds,
16757
18081
  });
16758
18082
  },
18083
+ title: async function () {
18084
+ return await render_runtime_html_asset(options);
18085
+ },
18086
+ style: async function () {
18087
+ return await render_runtime_html_asset(options);
18088
+ },
18089
+ meta: async function () {
18090
+ return await render_runtime_html_asset(options);
18091
+ },
18092
+ link: async function () {
18093
+ return await render_runtime_html_asset(options);
18094
+ },
18095
+ script: async function () {
18096
+ return await render_runtime_html_asset(options);
18097
+ },
16759
18098
  };
16760
18099
 
16761
- const renderer = renderers[options.nodeP.tagName];
18100
+ const renderer = renderers[normalized_render_tag_name];
16762
18101
  if (!renderer) {
16763
18102
  return { handled: false };
16764
18103
  }
@@ -16872,8 +18211,9 @@ func.runtime.widgets = func.runtime.widgets || {};
16872
18211
  // Browser-only render tree entrypoints live here so draw/cache helpers can stay focused.
16873
18212
 
16874
18213
  func.runtime.render.create_tree_runtime = function (options) {
18214
+ const render_node = options.nodeP || func.runtime.render.get_tree_source_node(options.treeP);
16875
18215
  const render_context = func.runtime.render.get_screen_context(options.SESSION_ID, options.$container, options.paramsP, options.is_skeleton);
16876
- const prop = func.runtime.render.get_node_attributes(options.nodeP);
18216
+ const prop = func.runtime.render.get_node_attributes(options.treeP || render_node);
16877
18217
  const is_mobile = render_context.is_mobile ? true : false;
16878
18218
  const hover_handlers = func.runtime.render.create_hover_handlers({
16879
18219
  SESSION_ID: options.SESSION_ID,
@@ -16888,13 +18228,22 @@ func.runtime.render.create_tree_runtime = function (options) {
16888
18228
  return await func.runtime.ui.close_modal_session(options.SESSION_ID, modal_id);
16889
18229
  };
16890
18230
  const iterate_child = async function ($divP, nodeP, parent_infoP, $root_container, before_record_function) {
18231
+ const child_tree = await func.runtime.render.ensure_tree_node({
18232
+ SESSION_ID: options.SESSION_ID,
18233
+ nodeP: nodeP || options.treeP || render_node,
18234
+ parent_infoP,
18235
+ paramsP: options.paramsP,
18236
+ keyP: options.keyP,
18237
+ parent_nodeP: render_node,
18238
+ pathP: options.treeP?.meta?.path || [],
18239
+ });
16891
18240
  return await func.runtime.render.iterate_children({
16892
18241
  $divP,
16893
- nodeP,
18242
+ nodeP: child_tree,
16894
18243
  is_mobile,
16895
18244
  before_record_function,
16896
18245
  render_child: async function (key, child) {
16897
- await options.render_child($divP, child, parent_infoP, key, nodeP, $root_container);
18246
+ await options.render_child($divP, child, parent_infoP, key, render_node, $root_container);
16898
18247
  },
16899
18248
  });
16900
18249
  };
@@ -16924,7 +18273,7 @@ func.runtime.render.draw_node = async function (options) {
16924
18273
  check_existP: options.check_existP,
16925
18274
  $root_container: options.$root_container,
16926
18275
  prop: options.prop,
16927
- element: options.nodeP.tagName,
18276
+ element: options.treeP?.tagName || options.nodeP.tagName,
16928
18277
  hover_handlers: options.hover_handlers,
16929
18278
  include_hover_click: options.include_hover_click,
16930
18279
  iterate_child: options.iterate_child,
@@ -16947,21 +18296,33 @@ func.runtime.render.draw_node = async function (options) {
16947
18296
  nodeP: options.nodeP,
16948
18297
  });
16949
18298
  };
16950
- 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) {
16951
- if (!nodeP) return;
16952
- const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'render_ui_tree');
16953
- func.runtime?.perf?.increment_map?.(SESSION_ID, 'render_node_counts', nodeP.id || nodeP.id_org || nodeP.tagName || 'unknown');
18299
+ func.runtime.render.render_tree = async function (treeP, renderer_context) {
18300
+ if (!treeP) return;
18301
+ const nodeP = func.runtime.render.get_tree_source_node(treeP);
18302
+ const perf_end = func.runtime?.perf?.start?.(renderer_context.SESSION_ID, 'render_ui_tree');
18303
+ func.runtime?.perf?.increment_map?.(renderer_context.SESSION_ID, 'render_node_counts', treeP.id || nodeP?.id || nodeP?.id_org || treeP.tagName || 'unknown');
16954
18304
  try {
16955
18305
  const tree_runtime = func.runtime.render.create_tree_runtime({
16956
- SESSION_ID,
16957
- $container,
18306
+ SESSION_ID: renderer_context.SESSION_ID,
18307
+ $container: renderer_context.$container,
18308
+ treeP,
16958
18309
  nodeP,
16959
- parent_infoP,
16960
- paramsP,
16961
- jobNoP,
16962
- is_skeleton,
18310
+ parent_infoP: renderer_context.parent_infoP,
18311
+ paramsP: renderer_context.paramsP,
18312
+ jobNoP: renderer_context.jobNoP,
18313
+ is_skeleton: renderer_context.is_skeleton,
18314
+ keyP: renderer_context.keyP,
16963
18315
  render_child: async function ($divP, child, parent_infoP, key, parentNodeP, rootContainerP) {
16964
- await func.runtime.render.render_ui_tree(SESSION_ID, $divP, child, parent_infoP, paramsP, jobNoP, is_skeleton, key, null, parentNodeP, null, rootContainerP);
18316
+ await func.runtime.render.render_tree(child, {
18317
+ ...renderer_context,
18318
+ $container: $divP,
18319
+ parent_infoP,
18320
+ keyP: key,
18321
+ refreshed_ds: null,
18322
+ parent_nodeP: parentNodeP,
18323
+ check_existP: null,
18324
+ $root_container: rootContainerP,
18325
+ });
16965
18326
  },
16966
18327
  });
16967
18328
  const render_context = tree_runtime.render_context;
@@ -16973,23 +18334,24 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16973
18334
  const iterate_child = tree_runtime.iterate_child;
16974
18335
 
16975
18336
  func.runtime.render.log_tree_debug({
16976
- SESSION_ID,
16977
- paramsP,
18337
+ SESSION_ID: renderer_context.SESSION_ID,
18338
+ paramsP: renderer_context.paramsP,
16978
18339
  nodeP,
16979
18340
  _ds,
16980
18341
  });
16981
18342
  const special_render = await func.runtime.render.render_special_node({
16982
- SESSION_ID,
16983
- $container,
16984
- $root_container,
18343
+ SESSION_ID: renderer_context.SESSION_ID,
18344
+ $container: renderer_context.$container,
18345
+ $root_container: renderer_context.$root_container,
18346
+ treeP,
16985
18347
  nodeP,
16986
- parent_infoP,
16987
- paramsP,
16988
- jobNoP,
16989
- is_skeleton,
16990
- keyP,
16991
- refreshed_ds,
16992
- parent_nodeP,
18348
+ parent_infoP: renderer_context.parent_infoP,
18349
+ paramsP: renderer_context.paramsP,
18350
+ jobNoP: renderer_context.jobNoP,
18351
+ is_skeleton: renderer_context.is_skeleton,
18352
+ keyP: renderer_context.keyP,
18353
+ refreshed_ds: renderer_context.refreshed_ds,
18354
+ parent_nodeP: renderer_context.parent_nodeP,
16993
18355
  prop,
16994
18356
  render_context,
16995
18357
  hover_handlers,
@@ -16997,22 +18359,23 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16997
18359
  close_modal,
16998
18360
  });
16999
18361
  if (special_render.handled) {
17000
- func.runtime?.perf?.increment?.(SESSION_ID, 'render_special_node_hits');
18362
+ func.runtime?.perf?.increment?.(renderer_context.SESSION_ID, 'render_special_node_hits');
17001
18363
  return special_render.result;
17002
18364
  }
17003
18365
  return await func.runtime.render.draw_node({
17004
- SESSION_ID,
17005
- $container,
17006
- $root_container,
18366
+ SESSION_ID: renderer_context.SESSION_ID,
18367
+ $container: renderer_context.$container,
18368
+ $root_container: renderer_context.$root_container,
18369
+ treeP,
17007
18370
  nodeP,
17008
- parent_infoP,
17009
- paramsP,
17010
- jobNoP,
17011
- is_skeleton,
17012
- keyP,
17013
- refreshed_ds,
17014
- parent_nodeP,
17015
- check_existP,
18371
+ parent_infoP: renderer_context.parent_infoP,
18372
+ paramsP: renderer_context.paramsP,
18373
+ jobNoP: renderer_context.jobNoP,
18374
+ is_skeleton: renderer_context.is_skeleton,
18375
+ keyP: renderer_context.keyP,
18376
+ refreshed_ds: renderer_context.refreshed_ds,
18377
+ parent_nodeP: renderer_context.parent_nodeP,
18378
+ check_existP: renderer_context.check_existP,
17016
18379
  prop,
17017
18380
  hover_handlers,
17018
18381
  include_hover_click,
@@ -17021,6 +18384,31 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
17021
18384
  } finally {
17022
18385
  perf_end?.();
17023
18386
  }
18387
+ };
18388
+ 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) {
18389
+ if (!nodeP) return;
18390
+ const treeP = await func.runtime.render.ensure_tree_node({
18391
+ SESSION_ID,
18392
+ nodeP,
18393
+ parent_infoP,
18394
+ paramsP,
18395
+ keyP,
18396
+ parent_nodeP,
18397
+ });
18398
+
18399
+ return await func.runtime.render.render_tree(treeP, {
18400
+ SESSION_ID,
18401
+ $container,
18402
+ parent_infoP,
18403
+ paramsP,
18404
+ jobNoP,
18405
+ is_skeleton,
18406
+ keyP,
18407
+ refreshed_ds,
18408
+ parent_nodeP,
18409
+ check_existP,
18410
+ $root_container,
18411
+ });
17024
18412
  };
17025
18413
  func.runtime = func.runtime || {};
17026
18414
  func.runtime.ui = func.runtime.ui || {};
@@ -18544,6 +19932,7 @@ func.runtime.render.handle_xu_panel_program = async function (options) {
18544
19932
  parent_infoP: options.parent_infoP,
18545
19933
  jobNoP: options.jobNoP,
18546
19934
  keyP: options.keyP,
19935
+ treeP: options.treeP,
18547
19936
  parent_nodeP: options.parent_nodeP,
18548
19937
  prop: options.nodeP.attributes,
18549
19938
  $appendToP: $wrapper,
@@ -22557,6 +23946,12 @@ function xuda(...args) {
22557
23946
  if (typeof opt !== 'object') {
22558
23947
  return console.error('Xuda Error - opt argument is not an object');
22559
23948
  }
23949
+
23950
+ if (!opt.ssr_payload && func.runtime.platform.get_window()?.__XUDA_SSR__) {
23951
+ opt.ssr_payload = func.runtime.platform.get_window().__XUDA_SSR__;
23952
+ }
23953
+ func.runtime.render.apply_runtime_bootstrap_defaults(opt);
23954
+
22560
23955
  glb.URL_PARAMS = func.common.getJsonFromUrl(platform.get_url_href());
22561
23956
 
22562
23957
  glb.worker_type = 'Worker';