@yuneta/gobj-ui 5.16.0 → 6.0.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.
@@ -6472,6 +6472,7 @@ function build_form_field_conf(gobj, field_desc) {
6472
6472
  field_conf.extras.pattern = "^-?\\d+(\\.\\d+)?$";
6473
6473
  break;
6474
6474
  case "rowid":
6475
+ case "qualified":
6475
6476
  field_conf.tag = "input";
6476
6477
  field_conf.inputType = "text";
6477
6478
  break;
@@ -6613,7 +6614,8 @@ function build_fkey_ref$1(gobj, field_desc, value) {
6613
6614
  /************************************************************
6614
6615
  * Apply the form mode to the pkey field:
6615
6616
  * "update" -> pkey readonly, not required
6616
- * "create" -> pkey writable and required (readonly if rowid)
6617
+ * "create" -> pkey writable and required, unless the store
6618
+ * hands the key out itself (rowid, qualified)
6617
6619
  * Empty form_mode keeps the template-declared behaviour
6618
6620
  * (backward compatible).
6619
6621
  ************************************************************/
@@ -6626,8 +6628,8 @@ function apply_form_mode(gobj, $form) {
6626
6628
  (0, _yuneta_gobj_js.log_error)((0, _yuneta_gobj_js.sprintf)("%s: form_mode '%s' but form has no pkey field '%s'", (0, _yuneta_gobj_js.gobj_short_name)(gobj), mode, pkey));
6627
6629
  return;
6628
6630
  }
6629
- let is_rowid = $input.field_desc && $input.field_desc.type === "rowid";
6630
- if (mode === "create" && !is_rowid) {
6631
+ let type = $input.field_desc ? $input.field_desc.type : "";
6632
+ if (mode === "create" && !(type === "rowid" || type === "qualified")) {
6631
6633
  $input.removeAttribute("readonly");
6632
6634
  $input.setAttribute("required", "");
6633
6635
  } else {
@@ -7405,6 +7407,7 @@ function template2columns(gobj, columns, template, sub_elements) {
7405
7407
  };
7406
7408
  break;
7407
7409
  case "rowid":
7410
+ case "qualified":
7408
7411
  column = null;
7409
7412
  break;
7410
7413
  case "boolean":
@@ -7770,6 +7773,7 @@ function treedb_value_2_form_value(gobj, field_desc, value) {
7770
7773
  case "enum":
7771
7774
  case "uuid":
7772
7775
  case "rowid":
7776
+ case "qualified":
7773
7777
  case "password":
7774
7778
  case "email":
7775
7779
  case "url": break;
@@ -7853,6 +7857,7 @@ function form_value_2_treedb_value(gobj, field_desc, value) {
7853
7857
  case "enum":
7854
7858
  case "uuid":
7855
7859
  case "rowid":
7860
+ case "qualified":
7856
7861
  case "password":
7857
7862
  case "email":
7858
7863
  case "url": break;
@@ -16930,7 +16935,8 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
16930
16935
  case "email":
16931
16936
  case "tel":
16932
16937
  case "url":
16933
- case "rowid": break;
16938
+ case "rowid":
16939
+ case "qualified": break;
16934
16940
  case "integer": break;
16935
16941
  case "real": break;
16936
16942
  case "boolean": break;
@@ -17393,6 +17399,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
17393
17399
  const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
17394
17400
  switch (field_desc.type) {
17395
17401
  case "rowid":
17402
+ case "qualified":
17396
17403
  if (operation === "create") value = "";
17397
17404
  break;
17398
17405
  case "string":
@@ -19767,11 +19774,26 @@ function register_c_yui_treedb_graph() {
19767
19774
  /***********************************************************************
19768
19775
  * c_yui_treedb_schema.js
19769
19776
  *
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
19777
+ * Schema-graph landing: the treedb drawn the way its `.c` literal
19778
+ * draws it in ASCII (treedb_schema_*.c, treedb_system_schema.c)
19779
+ * one CARD per topic listing its fields in schema order, one edge
19780
+ * per hook, from the row that declares the hook to the fkey row of
19781
+ * the child it names. Built from the schema `descs` alone: no data,
19782
+ * no backend calls.
19783
+ *
19784
+ * WHY THE CARD AND NOT A DOT. A topic is its fields; a schema is
19785
+ * read to find out what a topic holds and what links to what. A
19786
+ * graph of labelled dots answers neither, and the node graph next
19787
+ * door (C_G6_NODES_TREE) answers a different question entirely —
19788
+ * it draws the RECORDS, so on a treedb whose records are schemas
19789
+ * it draws one box per column, hundreds of them, each labelled by
19790
+ * a pkey that may be a rowid. This view draws the schema itself.
19791
+ *
19792
+ * The marks are the notation of the `.c` literals, so the drawing
19793
+ * and the source read the same — see schema_rows().
19794
+ *
19795
+ * A node click opens that topic's table (a real hash navigation
19796
+ * via the host-supplied `node_route`). An alternate landing to the
19775
19797
  * topic cards, in the spirit of "every treedb is a graph".
19776
19798
  *
19777
19799
  * Copyright (c) 2026, ArtGins.
@@ -19781,6 +19803,27 @@ function register_c_yui_treedb_graph() {
19781
19803
  * Constants
19782
19804
  ***************************************************************/
19783
19805
  var GCLASS_NAME$2 = "C_YUI_TREEDB_SCHEMA";
19806
+ /************************************************************
19807
+ * Geometry of a card, in one place: the row height is what
19808
+ * turns a field's index into the vertical position of its
19809
+ * port, so an edge leaves the very row that declares the
19810
+ * hook and lands on the row that declares the fkey.
19811
+ ************************************************************/
19812
+ var CARD_HEADER_H = 30;
19813
+ var CARD_ROW_H = 19;
19814
+ var CARD_PAD_V = 7;
19815
+ var CARD_MIN_W = 210;
19816
+ var CARD_MAX_W = 340;
19817
+ var topic_colors = [
19818
+ "rgb(237, 201, 73)",
19819
+ "rgb(118, 183, 178)",
19820
+ "rgb(255, 157, 167)",
19821
+ "rgb(175, 122, 161)",
19822
+ "rgb(89, 161, 79)",
19823
+ "rgb(186, 176, 171)",
19824
+ "rgb(66, 146, 198)"
19825
+ ];
19826
+ var SYSTEM_TOPIC_COLOR = "rgb(148, 163, 184)";
19784
19827
  /***************************************************************
19785
19828
  * Data
19786
19829
  ***************************************************************/
@@ -19842,48 +19885,254 @@ function build_ui$2(gobj) {
19842
19885
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
19843
19886
  }
19844
19887
  /************************************************************
19888
+ * The shape of a hook/fkey field, from the type it declares.
19889
+ * `dict` and `object` are the SAME thing to treedb (both build
19890
+ * a json object keyed by child id), and so are `list` and
19891
+ * `array` — see the hook/fkey switches in tr_treedb.c. A
19892
+ * `string` holds one reference, so it draws as one.
19893
+ ************************************************************/
19894
+ function hook_mark(type) {
19895
+ if (type === "dict" || type === "object") return "{}";
19896
+ if (type === "list" || type === "array") return "[]";
19897
+ return "()";
19898
+ }
19899
+ /************************************************************
19900
+ * The fields of one topic, as the schema declares them.
19901
+ *
19902
+ * The marks are the notation of the schema `.c` literals
19903
+ * (treedb_schema_*.c, treedb_system_schema.c), so the
19904
+ * drawing and the source read the same:
19905
+ * {} dict hook (N unique children)
19906
+ * [] list hook (n not-unique children)
19907
+ * () 1 child
19908
+ * (↖) 1 fkey (1 parent)
19909
+ * [↖] n fkeys (n parents)
19910
+ * {↖} N fkeys (N parents)
19911
+ * * required
19912
+ * # the primary key
19913
+ *
19914
+ * A column can be BOTH hook and fkey, so the flags are read
19915
+ * here rather than through treedb_get_field_desc's single
19916
+ * `type`, which keeps only the last flag it saw.
19917
+ ************************************************************/
19918
+ function schema_rows(desc) {
19919
+ let rows = [];
19920
+ if (!(0, _yuneta_gobj_js.is_object)(desc) || !Array.isArray(desc.cols)) return rows;
19921
+ for (let col of desc.cols) {
19922
+ if (!(0, _yuneta_gobj_js.is_object)(col) || !col.id) continue;
19923
+ let flags = Array.isArray(col.flag) ? col.flag : [];
19924
+ let is_hook = flags.indexOf("hook") >= 0;
19925
+ let is_fkey = flags.indexOf("fkey") >= 0;
19926
+ let mark = "";
19927
+ if (is_hook) mark = hook_mark(col.type);
19928
+ if (is_fkey) {
19929
+ let fmark = hook_mark(col.type).replace("}", "↖}").replace("]", "↖]").replace(")", "↖)");
19930
+ mark = mark ? mark + " " + fmark : fmark;
19931
+ }
19932
+ rows.push({
19933
+ name: col.id,
19934
+ type: col.type || "",
19935
+ mark,
19936
+ is_hook,
19937
+ is_fkey,
19938
+ is_pkey: col.id === desc.pkey,
19939
+ required: flags.indexOf("required") >= 0 || flags.indexOf("notnull") >= 0
19940
+ });
19941
+ }
19942
+ return rows;
19943
+ }
19944
+ /************************************************************
19945
+ * Size of the card that holds those rows. The width follows
19946
+ * the longest line so a long field name is not clipped into
19947
+ * an ellipsis — the point of the drawing is to read them.
19948
+ ************************************************************/
19949
+ function card_size(rows) {
19950
+ let longest = 0;
19951
+ for (let row of rows) {
19952
+ let len = row.name.length + (row.mark ? row.mark.length + 2 : 0) + 2;
19953
+ if (len > longest) longest = len;
19954
+ }
19955
+ let w = Math.round(26 + longest * 6.6);
19956
+ if (w < CARD_MIN_W) w = CARD_MIN_W;
19957
+ if (w > CARD_MAX_W) w = CARD_MAX_W;
19958
+ let h = 44 + rows.length * CARD_ROW_H;
19959
+ return [w, h];
19960
+ }
19961
+ /************************************************************
19962
+ * Vertical placement of a field's port, as a fraction of the
19963
+ * card height: the centre of its own row.
19964
+ ************************************************************/
19965
+ function row_placement(index, height) {
19966
+ return (37 + index * CARD_ROW_H + CARD_ROW_H / 2) / height;
19967
+ }
19968
+ /************************************************************
19969
+ * The card itself. Header = topic name on its topic colour;
19970
+ * body = one line per field, in schema order.
19971
+ ************************************************************/
19972
+ function build_card_innerHTML(topic, rows, color, dark) {
19973
+ let surface = dark ? "#1b2230" : "#ffffff";
19974
+ let bg = dark ? `color-mix(in srgb, ${color} 18%, #232b38)` : `color-mix(in srgb, ${color} 6%, ${surface})`;
19975
+ let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
19976
+ let header_bg = dark ? `color-mix(in srgb, ${color} 45%, #2c3542)` : `color-mix(in srgb, ${color} 28%, ${surface})`;
19977
+ let title_color = dark ? "#e8eaed" : "#0f172a";
19978
+ let text_color = dark ? "#d3d8de" : "#334155";
19979
+ let mute_color = dark ? "#98a2b0" : "#7c8694";
19980
+ let rule_color = dark ? "rgba(255,255,255,0.06)" : "rgba(15,23,42,0.05)";
19981
+ 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)";
19982
+ let rows_html = "";
19983
+ for (let row of rows) {
19984
+ let lead = row.is_pkey ? "#" : row.required ? "*" : "";
19985
+ let name_weight = row.is_pkey || row.required ? "600" : "400";
19986
+ let name_color = row.is_hook || row.is_fkey ? title_color : text_color;
19987
+ rows_html += ` <div class="TREEDB_SCHEMA_FIELD" style="
19988
+ display: flex; align-items: center; gap: 6px;
19989
+ height: ${CARD_ROW_H}px; line-height: ${CARD_ROW_H}px;
19990
+ padding: 0 10px; border-top: 1px solid ${rule_color};
19991
+ ">
19992
+ <span style="
19993
+ width: 8px; flex: 0 0 8px; text-align: center;
19994
+ color: ${mute_color}; font-size: 11px;
19995
+ ">${lead}</span>
19996
+ <span style="
19997
+ flex: 1 1 auto; min-width: 0; overflow: hidden;
19998
+ text-overflow: ellipsis; white-space: nowrap;
19999
+ font-size: 12px; font-weight: ${name_weight}; color: ${name_color};
20000
+ ">${(0, _yuneta_gobj_js.escapeHtml)(row.name)}</span>
20001
+ <span style="
20002
+ flex: 0 0 auto; font-size: 11px; color: ${mute_color};
20003
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
20004
+ ">${(0, _yuneta_gobj_js.escapeHtml)(row.mark)}</span>
20005
+ </div>
20006
+ `;
20007
+ }
20008
+ return `
20009
+ <div class="TREEDB_SCHEMA_CARD" title="${(0, _yuneta_gobj_js.escapeHtml)(topic)}" style="
20010
+ box-sizing: border-box;
20011
+ width: 100%;
20012
+ height: 100%;
20013
+ background: ${bg};
20014
+ border: 1.5px solid ${border};
20015
+ border-radius: 8px;
20016
+ box-shadow: ${shadow};
20017
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
20018
+ display: flex;
20019
+ flex-direction: column;
20020
+ overflow: hidden;
20021
+ cursor: pointer;
20022
+ ">
20023
+ <div class="TREEDB_SCHEMA_CARD_HEADER" style="
20024
+ height: ${CARD_HEADER_H}px; line-height: ${CARD_HEADER_H}px;
20025
+ flex: 0 0 ${CARD_HEADER_H}px;
20026
+ padding: 0 10px; background: ${header_bg}; color: ${title_color};
20027
+ font-size: 13px; font-weight: 700;
20028
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
20029
+ ">${(0, _yuneta_gobj_js.escapeHtml)(topic)}</div>
20030
+ <div class="TREEDB_SCHEMA_CARD_BODY" style="
20031
+ flex: 1 1 auto; padding: ${CARD_PAD_V}px 0; overflow: hidden;
20032
+ ">
20033
+ ${rows_html} </div>
20034
+ </div>
20035
+ `;
20036
+ }
20037
+ /************************************************************
19845
20038
  * 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.
20039
+ *
20040
+ * Node = a topic, drawn as the card the `.c` literal draws in
20041
+ * ASCII: its name and its fields. Edge = a hook, from the row
20042
+ * that declares it to the fkey row of the child it names —
20043
+ * `'hook': {'users': 'departments'}` says both ends, so the
20044
+ * arrow can land where the `.c` drawing lands it. An fkey
20045
+ * whose parent declares no hook still gets its edge, or a
20046
+ * half-declared schema would draw as disconnected.
20047
+ *
19848
20048
  * Left-to-right dagre follows the parent -> child data flow.
19849
20049
  ************************************************************/
19850
20050
  function schema_to_graph(gobj) {
19851
20051
  let descs = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "descs");
19852
20052
  let system = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "system");
20053
+ let dark = yui_is_dark();
19853
20054
  let nodes = [];
19854
- let topic_set = {};
20055
+ let cards = {};
19855
20056
  if (!(0, _yuneta_gobj_js.is_object)(descs)) return {
19856
20057
  nodes,
19857
20058
  edges: []
19858
20059
  };
20060
+ let idx = 0;
19859
20061
  for (let topic of Object.keys(descs)) {
19860
- if (!system && topic.substring(0, 2) === "__") continue;
19861
- topic_set[topic] = true;
20062
+ let is_system = topic.substring(0, 2) === "__";
20063
+ if (!system && is_system) continue;
20064
+ let rows = schema_rows(descs[topic]);
20065
+ let color;
20066
+ if (is_system) color = SYSTEM_TOPIC_COLOR;
20067
+ else {
20068
+ color = topic_colors[idx % topic_colors.length];
20069
+ idx++;
20070
+ }
20071
+ cards[topic] = {
20072
+ rows,
20073
+ size: card_size(rows),
20074
+ color
20075
+ };
20076
+ }
20077
+ for (let topic of Object.keys(cards)) {
20078
+ let card = cards[topic];
20079
+ let [w, h] = card.size;
20080
+ let ports = [];
20081
+ for (let i = 0; i < card.rows.length; i++) {
20082
+ let row = card.rows[i];
20083
+ if (!row.is_hook && !row.is_fkey) continue;
20084
+ ports.push({
20085
+ key: row.name,
20086
+ placement: [row.is_hook ? 1 : 0, row_placement(i, h)],
20087
+ fill: card.color,
20088
+ stroke: getStrokeColor(card.color)
20089
+ });
20090
+ }
19862
20091
  nodes.push({
19863
20092
  id: topic,
19864
- data: { topic_name: topic }
20093
+ type: "html",
20094
+ style: {
20095
+ size: [w, h],
20096
+ dx: -w / 2,
20097
+ dy: -h / 2,
20098
+ innerHTML: build_card_innerHTML(topic, card.rows, card.color, dark),
20099
+ port: ports.length > 0,
20100
+ ports,
20101
+ portR: 3,
20102
+ portLineWidth: 1
20103
+ },
20104
+ data: {
20105
+ topic_name: topic,
20106
+ rows: card.rows,
20107
+ color: card.color
20108
+ }
19865
20109
  });
19866
20110
  }
19867
20111
  let edge_seen = {};
19868
20112
  let edges = [];
19869
- let add_edge = (source, target) => {
19870
- if (!topic_set[source] || !topic_set[target] || source === target) return;
19871
- let key = source + "" + target;
20113
+ let add_edge = (source, source_port, target, target_port) => {
20114
+ if (!cards[source] || !cards[target]) return;
20115
+ let key = `${source}|${source_port}|${target}|${target_port}`;
19872
20116
  if (edge_seen[key]) return;
19873
20117
  edge_seen[key] = true;
19874
20118
  edges.push({
19875
20119
  id: key,
20120
+ type: "cubic",
19876
20121
  source,
19877
- target
20122
+ target,
20123
+ style: {
20124
+ sourcePort: source_port,
20125
+ targetPort: target_port
20126
+ }
19878
20127
  });
19879
20128
  };
19880
- for (let topic of Object.keys(descs)) {
20129
+ for (let topic of Object.keys(cards)) {
19881
20130
  let desc = descs[topic];
19882
20131
  if (!(0, _yuneta_gobj_js.is_object)(desc) || !Array.isArray(desc.cols)) continue;
19883
20132
  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);
20133
+ if (!(0, _yuneta_gobj_js.is_object)(col) || !col.id) continue;
20134
+ 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]);
20135
+ 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
20136
  }
19888
20137
  }
19889
20138
  return {
@@ -19892,26 +20141,15 @@ function schema_to_graph(gobj) {
19892
20141
  };
19893
20142
  }
19894
20143
  /************************************************************
19895
- * The theme-dependent styles, in ONE place: they are applied
19896
- * as the graph is built and re-applied on a theme switch
20144
+ * The theme-dependent edge style, in ONE place: applied as
20145
+ * the graph is built and re-applied on a theme switch
19897
20146
  * (ac_theme), which restyles the LIVE graph — rebuilding it
19898
20147
  * would drop the user's zoom/pan and any dragged node.
19899
20148
  ************************************************************/
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
20149
  function edge_style(dark) {
19913
20150
  return {
19914
- stroke: dark ? "#666666" : "#bbbbbb",
20151
+ stroke: dark ? "#7a8593" : "#9aa4b2",
20152
+ lineWidth: 1.2,
19915
20153
  endArrow: true
19916
20154
  };
19917
20155
  }
@@ -19943,13 +20181,12 @@ function build_graph$1(gobj) {
19943
20181
  container: $container,
19944
20182
  autoResize: true,
19945
20183
  data,
19946
- node: { style: node_style(dark) },
19947
20184
  edge: { style: edge_style(dark) },
19948
20185
  layout: {
19949
20186
  type: "antv-dagre",
19950
20187
  rankdir: "LR",
19951
- nodesep: 24,
19952
- ranksep: 60
20188
+ nodesep: 28,
20189
+ ranksep: 90
19953
20190
  },
19954
20191
  behaviors: [
19955
20192
  "zoom-canvas",
@@ -20034,8 +20271,17 @@ function ac_theme$1(gobj, event, kw, src) {
20034
20271
  let dark = kw && kw.theme ? kw.theme === "dark" : yui_is_dark();
20035
20272
  try {
20036
20273
  graph.setTheme(dark ? "dark" : "light");
20037
- graph.setNode({ style: node_style(dark) });
20038
20274
  graph.setEdge({ style: edge_style(dark) });
20275
+ let updates = [];
20276
+ for (let node of graph.getNodeData()) {
20277
+ let data = node.data;
20278
+ if (!(0, _yuneta_gobj_js.is_object)(data) || !Array.isArray(data.rows)) continue;
20279
+ updates.push({
20280
+ id: node.id,
20281
+ style: { innerHTML: build_card_innerHTML(data.topic_name, data.rows, data.color, dark) }
20282
+ });
20283
+ }
20284
+ if (updates.length > 0) graph.updateNodeData(updates);
20039
20285
  graph.draw().catch((e) => {
20040
20286
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: schema graph redraw failed: ${e}`);
20041
20287
  });
@@ -20100,6 +20346,61 @@ function register_c_yui_treedb_schema() {
20100
20346
  return create_gclass$2(GCLASS_NAME$2);
20101
20347
  }
20102
20348
  //#endregion
20349
+ //#region src/treedb_node_label.js
20350
+ /***********************************************************************
20351
+ * treedb_node_label.js
20352
+ *
20353
+ * Pure, testable logic behind the label of a node in the treedb
20354
+ * graph: WHAT A RECORD IS CALLED, which is not always what it is
20355
+ * keyed by. Kept out of the gclass so it can be unit-tested with
20356
+ * no DOM and no G6.
20357
+ *
20358
+ * Copyright (c) 2026, ArtGins.
20359
+ * All Rights Reserved.
20360
+ ***********************************************************************/
20361
+ /************************************************************
20362
+ * What a node is CALLED, which is not always what it is
20363
+ * keyed by.
20364
+ *
20365
+ * A topic whose id column is flagged `rowid`, `uuid` or
20366
+ * `qualified` keys its records by something that is not the
20367
+ * name — a counter, a random string, or the name with every
20368
+ * ancestor in front of it — and the name a human knows the
20369
+ * record by lives in the secondary key the topic declares
20370
+ * (`pkey2s`, carried in the desc since SDK > 7.13.0). The
20371
+ * `topics` and `cols` topics of treedb_system_schema are the
20372
+ * case that forced this: named in `value`, so every card in
20373
+ * the graph read "181", "225", "193" while they were keyed by
20374
+ * rowid, and reads the whole path now that they are
20375
+ * qualified.
20376
+ *
20377
+ * The pkey stays reachable: it is the card's tooltip.
20378
+ *
20379
+ * Falls back to the id whenever the schema does not say
20380
+ * otherwise — an older node whose desc carries no `pkey2s`
20381
+ * included.
20382
+ ************************************************************/
20383
+ function node_label(desc, record) {
20384
+ let id = record.id;
20385
+ let id_col = null;
20386
+ if (Array.isArray(desc.cols)) {
20387
+ for (let col of desc.cols) if (col && col.id === (desc.pkey || "id")) {
20388
+ id_col = col;
20389
+ break;
20390
+ }
20391
+ }
20392
+ if (!id_col || !Array.isArray(id_col.flag)) return id;
20393
+ if (id_col.flag.indexOf("rowid") < 0 && id_col.flag.indexOf("uuid") < 0 && id_col.flag.indexOf("qualified") < 0) return id;
20394
+ let pkey2s = desc.pkey2s;
20395
+ if (typeof pkey2s === "string") pkey2s = pkey2s ? [pkey2s] : [];
20396
+ if (!Array.isArray(pkey2s)) return id;
20397
+ for (let name of pkey2s) {
20398
+ let value = record[name];
20399
+ if (typeof value === "string" && value.length > 0) return value;
20400
+ }
20401
+ return id;
20402
+ }
20403
+ //#endregion
20103
20404
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
20104
20405
  function _typeof(o) {
20105
20406
  "@babel/helpers - typeof";
@@ -47632,19 +47933,19 @@ function create_topic_node(gobj, desc, record) {
47632
47933
  style.size = [116, 40];
47633
47934
  style.dx = -58;
47634
47935
  style.dy = -20;
47635
- style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, record.id);
47936
+ style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id);
47636
47937
  } else if (node_treedb_type === "extended") {
47637
47938
  node_graph_type = "html";
47638
47939
  style.size = [144, 66];
47639
47940
  style.dx = -72;
47640
47941
  style.dy = -33;
47641
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name, true);
47942
+ style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id);
47642
47943
  } else {
47643
47944
  node_graph_type = "html";
47644
47945
  style.size = [172, 96];
47645
47946
  style.dx = -86;
47646
47947
  style.dy = -48;
47647
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name);
47948
+ style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id);
47648
47949
  }
47649
47950
  let topic_props = priv._graph_properties[desc.topic_name];
47650
47951
  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 +48013,15 @@ function update_topic_node(gobj, desc, node_name, record) {
47712
48013
  nodedata.data.record = record;
47713
48014
  if (desc.node_treedb_type === "child") graph.updateNodeData([{
47714
48015
  id: node_name,
47715
- style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, record.id) }
48016
+ style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id) }
47716
48017
  }]);
47717
48018
  else if (desc.node_treedb_type === "extended") graph.updateNodeData([{
47718
48019
  id: node_name,
47719
- style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name, true) }
48020
+ style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id) }
47720
48021
  }]);
47721
48022
  else if (desc.node_treedb_type === "hierarchical") graph.updateNodeData([{
47722
48023
  id: node_name,
47723
- style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name) }
48024
+ style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id) }
47724
48025
  }]);
47725
48026
  } catch (e) {
47726
48027
  (0, _yuneta_gobj_js.log_error)(e.message);
@@ -49627,7 +49928,7 @@ function show_node_popover(gobj) {
49627
49928
  };
49628
49929
  if (node_graph_type === "hierarchical") {
49629
49930
  let record = nodeData.data.record || {};
49630
- updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, record.id, nodeData.data.desc.topic_name);
49931
+ 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
49932
  }
49632
49933
  graph.updateNodeData([{
49633
49934
  id: node_id,
@@ -49667,7 +49968,7 @@ function show_node_popover(gobj) {
49667
49968
  };
49668
49969
  if (node_graph_type === "hierarchical") {
49669
49970
  let record = nodeData.data.record || {};
49670
- restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, record.id, nodeData.data.desc.topic_name);
49971
+ 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
49972
  }
49672
49973
  graph.updateNodeData([{
49673
49974
  id: node_id,
@@ -49768,15 +50069,15 @@ function refresh_html_nodes_theme(gobj, theme) {
49768
50069
  let record = nd.data.record || {};
49769
50070
  if (tt === "hierarchical") updates.push({
49770
50071
  id: nodes[i].id,
49771
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, record.id, nd.data.desc.topic_name) }
50072
+ 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
50073
  });
49773
50074
  else if (tt === "child") updates.push({
49774
50075
  id: nodes[i].id,
49775
- style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, record.id) }
50076
+ style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), record.id) }
49776
50077
  });
49777
50078
  else if (tt === "extended") updates.push({
49778
50079
  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) }
50080
+ 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
50081
  });
49781
50082
  }
49782
50083
  if (updates.length > 0) graph.updateNodeData(updates);
@@ -49808,7 +50109,8 @@ function refresh_default_edges_theme(gobj, theme) {
49808
50109
  * but lighter (1px border, no shadow, single line). The name is
49809
50110
  * always legible (ellipsis + native title tooltip on overflow).
49810
50111
  ************************************************************/
49811
- function build_chip_innerHTML(color, theme, icon, id) {
50112
+ function build_chip_innerHTML(color, theme, icon, label, key) {
50113
+ let title = key || label;
49812
50114
  let dark = theme === "dark";
49813
50115
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
49814
50116
  let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
@@ -49819,7 +50121,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49819
50121
  margin-right: 6px; flex: 0 0 auto;
49820
50122
  "/>`;
49821
50123
  return `
49822
- <div title="${(0, _yuneta_gobj_js.escapeHtml)(id)}" style="
50124
+ <div title="${(0, _yuneta_gobj_js.escapeHtml)(title)}" style="
49823
50125
  box-sizing: border-box;
49824
50126
  width: 100%;
49825
50127
  height: 100%;
@@ -49836,7 +50138,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49836
50138
  ">${icon_html}<span style="
49837
50139
  font-size: 12px; font-weight: 600; line-height: 1;
49838
50140
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49839
- ">${(0, _yuneta_gobj_js.escapeHtml)(id)}</span>
50141
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>
49840
50142
  </div>
49841
50143
  `;
49842
50144
  }
@@ -49850,7 +50152,8 @@ function build_chip_innerHTML(color, theme, icon, id) {
49850
50152
  * colour is kept (per-topic differentiation) but softened via
49851
50153
  * color-mix instead of a harsh saturated fill. Theme-aware.
49852
50154
  ************************************************************/
49853
- function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
50155
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key) {
50156
+ let title = key || label;
49854
50157
  let dark = theme === "dark";
49855
50158
  let surface = dark ? "#1b2230" : "#ffffff";
49856
50159
  let bg, border, border_style;
@@ -49880,7 +50183,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49880
50183
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49881
50184
  ">${(0, _yuneta_gobj_js.escapeHtml)(topic_name)}</div>`;
49882
50185
  return `
49883
- <div title="${(0, _yuneta_gobj_js.escapeHtml)(id)}" style="
50186
+ <div title="${(0, _yuneta_gobj_js.escapeHtml)(title)}" style="
49884
50187
  box-sizing: border-box;
49885
50188
  width: 100%;
49886
50189
  height: 100%;
@@ -49903,7 +50206,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49903
50206
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
49904
50207
  display: -webkit-box; -webkit-line-clamp: 2;
49905
50208
  -webkit-box-orient: vertical; word-break: break-word;
49906
- ">${(0, _yuneta_gobj_js.escapeHtml)(id)}</div>${sub_html}
50209
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</div>${sub_html}
49907
50210
  </div>
49908
50211
  `;
49909
50212
  }
@@ -50124,7 +50427,7 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
50124
50427
  };
50125
50428
  if (nd.data.desc.node_treedb_type === "hierarchical") {
50126
50429
  let record = nd.data.record || {};
50127
- updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, record.id, nd.data.desc.topic_name);
50430
+ 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
50431
  }
50129
50432
  updates.push({
50130
50433
  id: nodes[i].id,