@yuneta/gobj-ui 5.16.0 → 5.17.0

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.
@@ -19767,11 +19767,26 @@ function register_c_yui_treedb_graph() {
19767
19767
  /***********************************************************************
19768
19768
  * c_yui_treedb_schema.js
19769
19769
  *
19770
- * Schema-graph landing (prototype): the treedb drawn as a GRAPH OF
19771
- * TOPICS one node per topic, one edge per hook/fkey relationship
19772
- * from the schema `descs` alone (no data, no backend calls). A
19773
- * node click opens that topic's table (a real hash navigation via
19774
- * the host-supplied `node_route`). An alternate landing to the
19770
+ * Schema-graph landing: the treedb drawn the way its `.c` literal
19771
+ * draws it in ASCII (treedb_schema_*.c, treedb_system_schema.c)
19772
+ * one CARD per topic listing its fields in schema order, one edge
19773
+ * per hook, from the row that declares the hook to the fkey row of
19774
+ * the child it names. Built from the schema `descs` alone: no data,
19775
+ * no backend calls.
19776
+ *
19777
+ * WHY THE CARD AND NOT A DOT. A topic is its fields; a schema is
19778
+ * read to find out what a topic holds and what links to what. A
19779
+ * graph of labelled dots answers neither, and the node graph next
19780
+ * door (C_G6_NODES_TREE) answers a different question entirely —
19781
+ * it draws the RECORDS, so on a treedb whose records are schemas
19782
+ * it draws one box per column, hundreds of them, each labelled by
19783
+ * a pkey that may be a rowid. This view draws the schema itself.
19784
+ *
19785
+ * The marks are the notation of the `.c` literals, so the drawing
19786
+ * and the source read the same — see schema_rows().
19787
+ *
19788
+ * A node click opens that topic's table (a real hash navigation
19789
+ * via the host-supplied `node_route`). An alternate landing to the
19775
19790
  * topic cards, in the spirit of "every treedb is a graph".
19776
19791
  *
19777
19792
  * Copyright (c) 2026, ArtGins.
@@ -19781,6 +19796,27 @@ function register_c_yui_treedb_graph() {
19781
19796
  * Constants
19782
19797
  ***************************************************************/
19783
19798
  var GCLASS_NAME$2 = "C_YUI_TREEDB_SCHEMA";
19799
+ /************************************************************
19800
+ * Geometry of a card, in one place: the row height is what
19801
+ * turns a field's index into the vertical position of its
19802
+ * port, so an edge leaves the very row that declares the
19803
+ * hook and lands on the row that declares the fkey.
19804
+ ************************************************************/
19805
+ var CARD_HEADER_H = 30;
19806
+ var CARD_ROW_H = 19;
19807
+ var CARD_PAD_V = 7;
19808
+ var CARD_MIN_W = 210;
19809
+ var CARD_MAX_W = 340;
19810
+ var topic_colors = [
19811
+ "rgb(237, 201, 73)",
19812
+ "rgb(118, 183, 178)",
19813
+ "rgb(255, 157, 167)",
19814
+ "rgb(175, 122, 161)",
19815
+ "rgb(89, 161, 79)",
19816
+ "rgb(186, 176, 171)",
19817
+ "rgb(66, 146, 198)"
19818
+ ];
19819
+ var SYSTEM_TOPIC_COLOR = "rgb(148, 163, 184)";
19784
19820
  /***************************************************************
19785
19821
  * Data
19786
19822
  ***************************************************************/
@@ -19842,48 +19878,254 @@ function build_ui$2(gobj) {
19842
19878
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
19843
19879
  }
19844
19880
  /************************************************************
19881
+ * The shape of a hook/fkey field, from the type it declares.
19882
+ * `dict` and `object` are the SAME thing to treedb (both build
19883
+ * a json object keyed by child id), and so are `list` and
19884
+ * `array` — see the hook/fkey switches in tr_treedb.c. A
19885
+ * `string` holds one reference, so it draws as one.
19886
+ ************************************************************/
19887
+ function hook_mark(type) {
19888
+ if (type === "dict" || type === "object") return "{}";
19889
+ if (type === "list" || type === "array") return "[]";
19890
+ return "()";
19891
+ }
19892
+ /************************************************************
19893
+ * The fields of one topic, as the schema declares them.
19894
+ *
19895
+ * The marks are the notation of the schema `.c` literals
19896
+ * (treedb_schema_*.c, treedb_system_schema.c), so the
19897
+ * drawing and the source read the same:
19898
+ * {} dict hook (N unique children)
19899
+ * [] list hook (n not-unique children)
19900
+ * () 1 child
19901
+ * (↖) 1 fkey (1 parent)
19902
+ * [↖] n fkeys (n parents)
19903
+ * {↖} N fkeys (N parents)
19904
+ * * required
19905
+ * # the primary key
19906
+ *
19907
+ * A column can be BOTH hook and fkey, so the flags are read
19908
+ * here rather than through treedb_get_field_desc's single
19909
+ * `type`, which keeps only the last flag it saw.
19910
+ ************************************************************/
19911
+ function schema_rows(desc) {
19912
+ let rows = [];
19913
+ if (!(0, _yuneta_gobj_js.is_object)(desc) || !Array.isArray(desc.cols)) return rows;
19914
+ for (let col of desc.cols) {
19915
+ if (!(0, _yuneta_gobj_js.is_object)(col) || !col.id) continue;
19916
+ let flags = Array.isArray(col.flag) ? col.flag : [];
19917
+ let is_hook = flags.indexOf("hook") >= 0;
19918
+ let is_fkey = flags.indexOf("fkey") >= 0;
19919
+ let mark = "";
19920
+ if (is_hook) mark = hook_mark(col.type);
19921
+ if (is_fkey) {
19922
+ let fmark = hook_mark(col.type).replace("}", "↖}").replace("]", "↖]").replace(")", "↖)");
19923
+ mark = mark ? mark + " " + fmark : fmark;
19924
+ }
19925
+ rows.push({
19926
+ name: col.id,
19927
+ type: col.type || "",
19928
+ mark,
19929
+ is_hook,
19930
+ is_fkey,
19931
+ is_pkey: col.id === desc.pkey,
19932
+ required: flags.indexOf("required") >= 0 || flags.indexOf("notnull") >= 0
19933
+ });
19934
+ }
19935
+ return rows;
19936
+ }
19937
+ /************************************************************
19938
+ * Size of the card that holds those rows. The width follows
19939
+ * the longest line so a long field name is not clipped into
19940
+ * an ellipsis — the point of the drawing is to read them.
19941
+ ************************************************************/
19942
+ function card_size(rows) {
19943
+ let longest = 0;
19944
+ for (let row of rows) {
19945
+ let len = row.name.length + (row.mark ? row.mark.length + 2 : 0) + 2;
19946
+ if (len > longest) longest = len;
19947
+ }
19948
+ let w = Math.round(26 + longest * 6.6);
19949
+ if (w < CARD_MIN_W) w = CARD_MIN_W;
19950
+ if (w > CARD_MAX_W) w = CARD_MAX_W;
19951
+ let h = 44 + rows.length * CARD_ROW_H;
19952
+ return [w, h];
19953
+ }
19954
+ /************************************************************
19955
+ * Vertical placement of a field's port, as a fraction of the
19956
+ * card height: the centre of its own row.
19957
+ ************************************************************/
19958
+ function row_placement(index, height) {
19959
+ return (37 + index * CARD_ROW_H + CARD_ROW_H / 2) / height;
19960
+ }
19961
+ /************************************************************
19962
+ * The card itself. Header = topic name on its topic colour;
19963
+ * body = one line per field, in schema order.
19964
+ ************************************************************/
19965
+ function build_card_innerHTML(topic, rows, color, dark) {
19966
+ let surface = dark ? "#1b2230" : "#ffffff";
19967
+ let bg = dark ? `color-mix(in srgb, ${color} 18%, #232b38)` : `color-mix(in srgb, ${color} 6%, ${surface})`;
19968
+ let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
19969
+ let header_bg = dark ? `color-mix(in srgb, ${color} 45%, #2c3542)` : `color-mix(in srgb, ${color} 28%, ${surface})`;
19970
+ let title_color = dark ? "#e8eaed" : "#0f172a";
19971
+ let text_color = dark ? "#d3d8de" : "#334155";
19972
+ let mute_color = dark ? "#98a2b0" : "#7c8694";
19973
+ let rule_color = dark ? "rgba(255,255,255,0.06)" : "rgba(15,23,42,0.05)";
19974
+ let shadow = dark ? "0 1px 3px rgba(0,0,0,0.45), 0 1px 2px rgba(0,0,0,0.30)" : "0 1px 3px rgba(15,23,42,0.12), 0 1px 2px rgba(15,23,42,0.06)";
19975
+ let rows_html = "";
19976
+ for (let row of rows) {
19977
+ let lead = row.is_pkey ? "#" : row.required ? "*" : "";
19978
+ let name_weight = row.is_pkey || row.required ? "600" : "400";
19979
+ let name_color = row.is_hook || row.is_fkey ? title_color : text_color;
19980
+ rows_html += ` <div class="TREEDB_SCHEMA_FIELD" style="
19981
+ display: flex; align-items: center; gap: 6px;
19982
+ height: ${CARD_ROW_H}px; line-height: ${CARD_ROW_H}px;
19983
+ padding: 0 10px; border-top: 1px solid ${rule_color};
19984
+ ">
19985
+ <span style="
19986
+ width: 8px; flex: 0 0 8px; text-align: center;
19987
+ color: ${mute_color}; font-size: 11px;
19988
+ ">${lead}</span>
19989
+ <span style="
19990
+ flex: 1 1 auto; min-width: 0; overflow: hidden;
19991
+ text-overflow: ellipsis; white-space: nowrap;
19992
+ font-size: 12px; font-weight: ${name_weight}; color: ${name_color};
19993
+ ">${(0, _yuneta_gobj_js.escapeHtml)(row.name)}</span>
19994
+ <span style="
19995
+ flex: 0 0 auto; font-size: 11px; color: ${mute_color};
19996
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
19997
+ ">${(0, _yuneta_gobj_js.escapeHtml)(row.mark)}</span>
19998
+ </div>
19999
+ `;
20000
+ }
20001
+ return `
20002
+ <div class="TREEDB_SCHEMA_CARD" title="${(0, _yuneta_gobj_js.escapeHtml)(topic)}" style="
20003
+ box-sizing: border-box;
20004
+ width: 100%;
20005
+ height: 100%;
20006
+ background: ${bg};
20007
+ border: 1.5px solid ${border};
20008
+ border-radius: 8px;
20009
+ box-shadow: ${shadow};
20010
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
20011
+ display: flex;
20012
+ flex-direction: column;
20013
+ overflow: hidden;
20014
+ cursor: pointer;
20015
+ ">
20016
+ <div class="TREEDB_SCHEMA_CARD_HEADER" style="
20017
+ height: ${CARD_HEADER_H}px; line-height: ${CARD_HEADER_H}px;
20018
+ flex: 0 0 ${CARD_HEADER_H}px;
20019
+ padding: 0 10px; background: ${header_bg}; color: ${title_color};
20020
+ font-size: 13px; font-weight: 700;
20021
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
20022
+ ">${(0, _yuneta_gobj_js.escapeHtml)(topic)}</div>
20023
+ <div class="TREEDB_SCHEMA_CARD_BODY" style="
20024
+ flex: 1 1 auto; padding: ${CARD_PAD_V}px 0; overflow: hidden;
20025
+ ">
20026
+ ${rows_html} </div>
20027
+ </div>
20028
+ `;
20029
+ }
20030
+ /************************************************************
19845
20031
  * Derive {nodes, edges} from the schema.
19846
- * Node = a topic. Edge = a hook (parent -> child) or an
19847
- * fkey (child -> parent, reversed to parent -> child), deduped.
20032
+ *
20033
+ * Node = a topic, drawn as the card the `.c` literal draws in
20034
+ * ASCII: its name and its fields. Edge = a hook, from the row
20035
+ * that declares it to the fkey row of the child it names —
20036
+ * `'hook': {'users': 'departments'}` says both ends, so the
20037
+ * arrow can land where the `.c` drawing lands it. An fkey
20038
+ * whose parent declares no hook still gets its edge, or a
20039
+ * half-declared schema would draw as disconnected.
20040
+ *
19848
20041
  * Left-to-right dagre follows the parent -> child data flow.
19849
20042
  ************************************************************/
19850
20043
  function schema_to_graph(gobj) {
19851
20044
  let descs = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "descs");
19852
20045
  let system = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "system");
20046
+ let dark = yui_is_dark();
19853
20047
  let nodes = [];
19854
- let topic_set = {};
20048
+ let cards = {};
19855
20049
  if (!(0, _yuneta_gobj_js.is_object)(descs)) return {
19856
20050
  nodes,
19857
20051
  edges: []
19858
20052
  };
20053
+ let idx = 0;
19859
20054
  for (let topic of Object.keys(descs)) {
19860
- if (!system && topic.substring(0, 2) === "__") continue;
19861
- topic_set[topic] = true;
20055
+ let is_system = topic.substring(0, 2) === "__";
20056
+ if (!system && is_system) continue;
20057
+ let rows = schema_rows(descs[topic]);
20058
+ let color;
20059
+ if (is_system) color = SYSTEM_TOPIC_COLOR;
20060
+ else {
20061
+ color = topic_colors[idx % topic_colors.length];
20062
+ idx++;
20063
+ }
20064
+ cards[topic] = {
20065
+ rows,
20066
+ size: card_size(rows),
20067
+ color
20068
+ };
20069
+ }
20070
+ for (let topic of Object.keys(cards)) {
20071
+ let card = cards[topic];
20072
+ let [w, h] = card.size;
20073
+ let ports = [];
20074
+ for (let i = 0; i < card.rows.length; i++) {
20075
+ let row = card.rows[i];
20076
+ if (!row.is_hook && !row.is_fkey) continue;
20077
+ ports.push({
20078
+ key: row.name,
20079
+ placement: [row.is_hook ? 1 : 0, row_placement(i, h)],
20080
+ fill: card.color,
20081
+ stroke: getStrokeColor(card.color)
20082
+ });
20083
+ }
19862
20084
  nodes.push({
19863
20085
  id: topic,
19864
- data: { topic_name: topic }
20086
+ type: "html",
20087
+ style: {
20088
+ size: [w, h],
20089
+ dx: -w / 2,
20090
+ dy: -h / 2,
20091
+ innerHTML: build_card_innerHTML(topic, card.rows, card.color, dark),
20092
+ port: ports.length > 0,
20093
+ ports,
20094
+ portR: 3,
20095
+ portLineWidth: 1
20096
+ },
20097
+ data: {
20098
+ topic_name: topic,
20099
+ rows: card.rows,
20100
+ color: card.color
20101
+ }
19865
20102
  });
19866
20103
  }
19867
20104
  let edge_seen = {};
19868
20105
  let edges = [];
19869
- let add_edge = (source, target) => {
19870
- if (!topic_set[source] || !topic_set[target] || source === target) return;
19871
- let key = source + "" + target;
20106
+ let add_edge = (source, source_port, target, target_port) => {
20107
+ if (!cards[source] || !cards[target]) return;
20108
+ let key = `${source}|${source_port}|${target}|${target_port}`;
19872
20109
  if (edge_seen[key]) return;
19873
20110
  edge_seen[key] = true;
19874
20111
  edges.push({
19875
20112
  id: key,
20113
+ type: "cubic",
19876
20114
  source,
19877
- target
20115
+ target,
20116
+ style: {
20117
+ sourcePort: source_port,
20118
+ targetPort: target_port
20119
+ }
19878
20120
  });
19879
20121
  };
19880
- for (let topic of Object.keys(descs)) {
20122
+ for (let topic of Object.keys(cards)) {
19881
20123
  let desc = descs[topic];
19882
20124
  if (!(0, _yuneta_gobj_js.is_object)(desc) || !Array.isArray(desc.cols)) continue;
19883
20125
  for (let col of desc.cols) {
19884
- if (!col) continue;
19885
- if ((0, _yuneta_gobj_js.is_object)(col.hook)) for (let child of Object.keys(col.hook)) add_edge(topic, child);
19886
- else if ((0, _yuneta_gobj_js.is_object)(col.fkey)) for (let parent of Object.keys(col.fkey)) add_edge(parent, topic);
20126
+ if (!(0, _yuneta_gobj_js.is_object)(col) || !col.id) continue;
20127
+ if ((0, _yuneta_gobj_js.is_object)(col.hook)) for (let child of Object.keys(col.hook)) add_edge(topic, col.id, child, col.hook[child]);
20128
+ if ((0, _yuneta_gobj_js.is_object)(col.fkey)) for (let parent of Object.keys(col.fkey)) add_edge(parent, col.fkey[parent], topic, col.id);
19887
20129
  }
19888
20130
  }
19889
20131
  return {
@@ -19892,26 +20134,15 @@ function schema_to_graph(gobj) {
19892
20134
  };
19893
20135
  }
19894
20136
  /************************************************************
19895
- * The theme-dependent styles, in ONE place: they are applied
19896
- * as the graph is built and re-applied on a theme switch
20137
+ * The theme-dependent edge style, in ONE place: applied as
20138
+ * the graph is built and re-applied on a theme switch
19897
20139
  * (ac_theme), which restyles the LIVE graph — rebuilding it
19898
20140
  * would drop the user's zoom/pan and any dragged node.
19899
20141
  ************************************************************/
19900
- function node_style(dark) {
19901
- return {
19902
- size: 40,
19903
- fill: "#5B8FF9",
19904
- labelText: (d) => d.id,
19905
- labelPlacement: "bottom",
19906
- labelFill: dark ? "#e6e6e6" : "#333333",
19907
- labelBackground: true,
19908
- labelBackgroundFill: dark ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.7)",
19909
- cursor: "pointer"
19910
- };
19911
- }
19912
20142
  function edge_style(dark) {
19913
20143
  return {
19914
- stroke: dark ? "#666666" : "#bbbbbb",
20144
+ stroke: dark ? "#7a8593" : "#9aa4b2",
20145
+ lineWidth: 1.2,
19915
20146
  endArrow: true
19916
20147
  };
19917
20148
  }
@@ -19943,13 +20174,12 @@ function build_graph$1(gobj) {
19943
20174
  container: $container,
19944
20175
  autoResize: true,
19945
20176
  data,
19946
- node: { style: node_style(dark) },
19947
20177
  edge: { style: edge_style(dark) },
19948
20178
  layout: {
19949
20179
  type: "antv-dagre",
19950
20180
  rankdir: "LR",
19951
- nodesep: 24,
19952
- ranksep: 60
20181
+ nodesep: 28,
20182
+ ranksep: 90
19953
20183
  },
19954
20184
  behaviors: [
19955
20185
  "zoom-canvas",
@@ -20034,8 +20264,17 @@ function ac_theme$1(gobj, event, kw, src) {
20034
20264
  let dark = kw && kw.theme ? kw.theme === "dark" : yui_is_dark();
20035
20265
  try {
20036
20266
  graph.setTheme(dark ? "dark" : "light");
20037
- graph.setNode({ style: node_style(dark) });
20038
20267
  graph.setEdge({ style: edge_style(dark) });
20268
+ let updates = [];
20269
+ for (let node of graph.getNodeData()) {
20270
+ let data = node.data;
20271
+ if (!(0, _yuneta_gobj_js.is_object)(data) || !Array.isArray(data.rows)) continue;
20272
+ updates.push({
20273
+ id: node.id,
20274
+ style: { innerHTML: build_card_innerHTML(data.topic_name, data.rows, data.color, dark) }
20275
+ });
20276
+ }
20277
+ if (updates.length > 0) graph.updateNodeData(updates);
20039
20278
  graph.draw().catch((e) => {
20040
20279
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: schema graph redraw failed: ${e}`);
20041
20280
  });
@@ -20100,6 +20339,58 @@ function register_c_yui_treedb_schema() {
20100
20339
  return create_gclass$2(GCLASS_NAME$2);
20101
20340
  }
20102
20341
  //#endregion
20342
+ //#region src/treedb_node_label.js
20343
+ /***********************************************************************
20344
+ * treedb_node_label.js
20345
+ *
20346
+ * Pure, testable logic behind the label of a node in the treedb
20347
+ * graph: WHAT A RECORD IS CALLED, which is not always what it is
20348
+ * keyed by. Kept out of the gclass so it can be unit-tested with
20349
+ * no DOM and no G6.
20350
+ *
20351
+ * Copyright (c) 2026, ArtGins.
20352
+ * All Rights Reserved.
20353
+ ***********************************************************************/
20354
+ /************************************************************
20355
+ * What a node is CALLED, which is not always what it is
20356
+ * keyed by.
20357
+ *
20358
+ * A topic whose id column is flagged `rowid` or `uuid` keys
20359
+ * its records by a value nobody reads — that is the point of
20360
+ * those flags — and the name a human knows the record by
20361
+ * lives in the secondary key the topic declares (`pkey2s`,
20362
+ * carried in the desc since SDK > 7.13.0). The `topics` and
20363
+ * `cols` topics of treedb_system_schema are the case that
20364
+ * forced this: keyed by rowid, named in `value`, so every
20365
+ * card in the graph read "181", "225", "193".
20366
+ *
20367
+ * The pkey stays reachable: it is the card's tooltip.
20368
+ *
20369
+ * Falls back to the id whenever the schema does not say
20370
+ * otherwise — an older node whose desc carries no `pkey2s`
20371
+ * included.
20372
+ ************************************************************/
20373
+ function node_label(desc, record) {
20374
+ let id = record.id;
20375
+ let id_col = null;
20376
+ if (Array.isArray(desc.cols)) {
20377
+ for (let col of desc.cols) if (col && col.id === (desc.pkey || "id")) {
20378
+ id_col = col;
20379
+ break;
20380
+ }
20381
+ }
20382
+ if (!id_col || !Array.isArray(id_col.flag)) return id;
20383
+ if (id_col.flag.indexOf("rowid") < 0 && id_col.flag.indexOf("uuid") < 0) return id;
20384
+ let pkey2s = desc.pkey2s;
20385
+ if (typeof pkey2s === "string") pkey2s = pkey2s ? [pkey2s] : [];
20386
+ if (!Array.isArray(pkey2s)) return id;
20387
+ for (let name of pkey2s) {
20388
+ let value = record[name];
20389
+ if (typeof value === "string" && value.length > 0) return value;
20390
+ }
20391
+ return id;
20392
+ }
20393
+ //#endregion
20103
20394
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
20104
20395
  function _typeof(o) {
20105
20396
  "@babel/helpers - typeof";
@@ -47632,19 +47923,19 @@ function create_topic_node(gobj, desc, record) {
47632
47923
  style.size = [116, 40];
47633
47924
  style.dx = -58;
47634
47925
  style.dy = -20;
47635
- style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, record.id);
47926
+ style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id);
47636
47927
  } else if (node_treedb_type === "extended") {
47637
47928
  node_graph_type = "html";
47638
47929
  style.size = [144, 66];
47639
47930
  style.dx = -72;
47640
47931
  style.dy = -33;
47641
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name, true);
47932
+ style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id);
47642
47933
  } else {
47643
47934
  node_graph_type = "html";
47644
47935
  style.size = [172, 96];
47645
47936
  style.dx = -86;
47646
47937
  style.dy = -48;
47647
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name);
47938
+ style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id);
47648
47939
  }
47649
47940
  let topic_props = priv._graph_properties[desc.topic_name];
47650
47941
  let topic_defaults = (0, _yuneta_gobj_js.is_object)(topic_props) && (0, _yuneta_gobj_js.is_object)(topic_props.defaults) ? topic_props.defaults : null;
@@ -47712,15 +48003,15 @@ function update_topic_node(gobj, desc, node_name, record) {
47712
48003
  nodedata.data.record = record;
47713
48004
  if (desc.node_treedb_type === "child") graph.updateNodeData([{
47714
48005
  id: node_name,
47715
- style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, record.id) }
48006
+ style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id) }
47716
48007
  }]);
47717
48008
  else if (desc.node_treedb_type === "extended") graph.updateNodeData([{
47718
48009
  id: node_name,
47719
- style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name, true) }
48010
+ style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id) }
47720
48011
  }]);
47721
48012
  else if (desc.node_treedb_type === "hierarchical") graph.updateNodeData([{
47722
48013
  id: node_name,
47723
- style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name) }
48014
+ style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id) }
47724
48015
  }]);
47725
48016
  } catch (e) {
47726
48017
  (0, _yuneta_gobj_js.log_error)(e.message);
@@ -49627,7 +49918,7 @@ function show_node_popover(gobj) {
49627
49918
  };
49628
49919
  if (node_graph_type === "hierarchical") {
49629
49920
  let record = nodeData.data.record || {};
49630
- updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, record.id, nodeData.data.desc.topic_name);
49921
+ updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id);
49631
49922
  }
49632
49923
  graph.updateNodeData([{
49633
49924
  id: node_id,
@@ -49667,7 +49958,7 @@ function show_node_popover(gobj) {
49667
49958
  };
49668
49959
  if (node_graph_type === "hierarchical") {
49669
49960
  let record = nodeData.data.record || {};
49670
- restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, record.id, nodeData.data.desc.topic_name);
49961
+ restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id);
49671
49962
  }
49672
49963
  graph.updateNodeData([{
49673
49964
  id: node_id,
@@ -49768,15 +50059,15 @@ function refresh_html_nodes_theme(gobj, theme) {
49768
50059
  let record = nd.data.record || {};
49769
50060
  if (tt === "hierarchical") updates.push({
49770
50061
  id: nodes[i].id,
49771
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, record.id, nd.data.desc.topic_name) }
50062
+ style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id) }
49772
50063
  });
49773
50064
  else if (tt === "child") updates.push({
49774
50065
  id: nodes[i].id,
49775
- style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, record.id) }
50066
+ style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), record.id) }
49776
50067
  });
49777
50068
  else if (tt === "extended") updates.push({
49778
50069
  id: nodes[i].id,
49779
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, record.id, nd.data.desc.topic_name, true) }
50070
+ style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, true, record.id) }
49780
50071
  });
49781
50072
  }
49782
50073
  if (updates.length > 0) graph.updateNodeData(updates);
@@ -49808,7 +50099,8 @@ function refresh_default_edges_theme(gobj, theme) {
49808
50099
  * but lighter (1px border, no shadow, single line). The name is
49809
50100
  * always legible (ellipsis + native title tooltip on overflow).
49810
50101
  ************************************************************/
49811
- function build_chip_innerHTML(color, theme, icon, id) {
50102
+ function build_chip_innerHTML(color, theme, icon, label, key) {
50103
+ let title = key || label;
49812
50104
  let dark = theme === "dark";
49813
50105
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
49814
50106
  let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
@@ -49819,7 +50111,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49819
50111
  margin-right: 6px; flex: 0 0 auto;
49820
50112
  "/>`;
49821
50113
  return `
49822
- <div title="${(0, _yuneta_gobj_js.escapeHtml)(id)}" style="
50114
+ <div title="${(0, _yuneta_gobj_js.escapeHtml)(title)}" style="
49823
50115
  box-sizing: border-box;
49824
50116
  width: 100%;
49825
50117
  height: 100%;
@@ -49836,7 +50128,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49836
50128
  ">${icon_html}<span style="
49837
50129
  font-size: 12px; font-weight: 600; line-height: 1;
49838
50130
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49839
- ">${(0, _yuneta_gobj_js.escapeHtml)(id)}</span>
50131
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>
49840
50132
  </div>
49841
50133
  `;
49842
50134
  }
@@ -49850,7 +50142,8 @@ function build_chip_innerHTML(color, theme, icon, id) {
49850
50142
  * colour is kept (per-topic differentiation) but softened via
49851
50143
  * color-mix instead of a harsh saturated fill. Theme-aware.
49852
50144
  ************************************************************/
49853
- function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
50145
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key) {
50146
+ let title = key || label;
49854
50147
  let dark = theme === "dark";
49855
50148
  let surface = dark ? "#1b2230" : "#ffffff";
49856
50149
  let bg, border, border_style;
@@ -49880,7 +50173,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49880
50173
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49881
50174
  ">${(0, _yuneta_gobj_js.escapeHtml)(topic_name)}</div>`;
49882
50175
  return `
49883
- <div title="${(0, _yuneta_gobj_js.escapeHtml)(id)}" style="
50176
+ <div title="${(0, _yuneta_gobj_js.escapeHtml)(title)}" style="
49884
50177
  box-sizing: border-box;
49885
50178
  width: 100%;
49886
50179
  height: 100%;
@@ -49903,7 +50196,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49903
50196
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
49904
50197
  display: -webkit-box; -webkit-line-clamp: 2;
49905
50198
  -webkit-box-orient: vertical; word-break: break-word;
49906
- ">${(0, _yuneta_gobj_js.escapeHtml)(id)}</div>${sub_html}
50199
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</div>${sub_html}
49907
50200
  </div>
49908
50201
  `;
49909
50202
  }
@@ -50124,7 +50417,7 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
50124
50417
  };
50125
50418
  if (nd.data.desc.node_treedb_type === "hierarchical") {
50126
50419
  let record = nd.data.record || {};
50127
- updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, record.id, nd.data.desc.topic_name);
50420
+ updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id);
50128
50421
  }
50129
50422
  updates.push({
50130
50423
  id: nodes[i].id,