@yuneta/gobj-ui 5.15.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.
@@ -15505,7 +15505,7 @@ function request_print_tranger$1(gobj, path) {
15505
15505
  * would travel, be refused by the yuno, and come back as a toast --
15506
15506
  * after the table had already drawn the row.
15507
15507
  ***************************************************************/
15508
- function refuse_if_readonly$1(gobj, event) {
15508
+ function refuse_if_readonly$2(gobj, event) {
15509
15509
  if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) return false;
15510
15510
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, treedb '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}' is READ-ONLY`);
15511
15511
  return true;
@@ -15577,7 +15577,8 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
15577
15577
  treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
15578
15578
  topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
15579
15579
  record: (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0),
15580
- created: command === "create-node"
15580
+ created: command === "create-node",
15581
+ command
15581
15582
  });
15582
15583
  break;
15583
15584
  case "delete-node": break;
@@ -15782,7 +15783,7 @@ function ac_treedb_node_deleted$1(gobj, event, kw, src) {
15782
15783
  * }
15783
15784
  ********************************************/
15784
15785
  function ac_create_record(gobj, event, kw, src) {
15785
- if (refuse_if_readonly$1(gobj, event)) return -1;
15786
+ if (refuse_if_readonly$2(gobj, event)) return -1;
15786
15787
  let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
15787
15788
  let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
15788
15789
  let record = kw.record;
@@ -15800,7 +15801,7 @@ function ac_create_record(gobj, event, kw, src) {
15800
15801
  * }
15801
15802
  ********************************************/
15802
15803
  function ac_update_record(gobj, event, kw, src) {
15803
- if (refuse_if_readonly$1(gobj, event)) return -1;
15804
+ if (refuse_if_readonly$2(gobj, event)) return -1;
15804
15805
  let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
15805
15806
  let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
15806
15807
  let record = kw.record;
@@ -15817,7 +15818,7 @@ function ac_update_record(gobj, event, kw, src) {
15817
15818
  * }
15818
15819
  ********************************************/
15819
15820
  function ac_delete_record(gobj, event, kw, src) {
15820
- if (refuse_if_readonly$1(gobj, event)) return -1;
15821
+ if (refuse_if_readonly$2(gobj, event)) return -1;
15821
15822
  let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
15822
15823
  let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
15823
15824
  let record = kw.record;
@@ -17638,7 +17639,7 @@ function write_plan(gobj) {
17638
17639
  * A read-only topic refuses the write EVENTS too, not only their
17639
17640
  * buttons.
17640
17641
  ***************************************************************/
17641
- function refuse_if_readonly(gobj, event) {
17642
+ function refuse_if_readonly$1(gobj, event) {
17642
17643
  if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) return false;
17643
17644
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, topic '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name")}' is READ-ONLY`);
17644
17645
  return true;
@@ -17710,7 +17711,10 @@ function ac_load_node_updated(gobj, event, kw, src) {
17710
17711
  return -1;
17711
17712
  }
17712
17713
  let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
17713
- if (tabulator) for (let record of data) tabulator.updateData([record]);
17714
+ if (tabulator) {
17715
+ for (let record of data) if (tabulator.getRow(record.id)) tabulator.updateData([record]);
17716
+ else if (tabulator.getDataCount() > 0) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: updated node is not in the loaded table: ${record.id}`);
17717
+ }
17714
17718
  return 0;
17715
17719
  }
17716
17720
  /************************************************************
@@ -17735,7 +17739,7 @@ function ac_node_deleted$1(gobj, event, kw, src) {
17735
17739
  * and the Op column is visible (with row's edit/delete icons)
17736
17740
  ************************************************************/
17737
17741
  function ac_edition_mode(gobj, event, kw, src) {
17738
- if (refuse_if_readonly(gobj, event)) return -1;
17742
+ if (refuse_if_readonly$1(gobj, event)) return -1;
17739
17743
  let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
17740
17744
  let editable = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "editable");
17741
17745
  editable = !editable;
@@ -17779,7 +17783,7 @@ function ac_edition_mode(gobj, event, kw, src) {
17779
17783
  * - the backend will broadcast the new record that will be added to table
17780
17784
  ************************************************************/
17781
17785
  function ac_new_row(gobj, event, kw, src) {
17782
- if (refuse_if_readonly(gobj, event)) return -1;
17786
+ if (refuse_if_readonly$1(gobj, event)) return -1;
17783
17787
  let row = {};
17784
17788
  let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
17785
17789
  for (let i = 0; i < desc.cols.length; i++) {
@@ -17809,7 +17813,7 @@ function ac_new_row(gobj, event, kw, src) {
17809
17813
  * kw {index:, row:}
17810
17814
  ************************************************************/
17811
17815
  function ac_delete_rows(gobj, event, kw, src) {
17812
- if (refuse_if_readonly(gobj, event)) return -1;
17816
+ if (refuse_if_readonly$1(gobj, event)) return -1;
17813
17817
  let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
17814
17818
  if ((0, _yuneta_gobj_js.json_size)(kw) === 0) {
17815
17819
  let rows = tabulator.getSelectedData();
@@ -17873,7 +17877,7 @@ function ac_copy_rows(gobj, event, kw, src) {
17873
17877
  * From internal
17874
17878
  ************************************************************/
17875
17879
  function ac_paste_rows(gobj, event, kw, src) {
17876
- if (refuse_if_readonly(gobj, event)) return -1;
17880
+ if (refuse_if_readonly$1(gobj, event)) return -1;
17877
17881
  if (kw.topic_name === (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name") && kw.treedb_name === (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")) for (let row of kw.rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_CREATE_RECORD", {
17878
17882
  topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
17879
17883
  record: row
@@ -17886,7 +17890,7 @@ function ac_paste_rows(gobj, event, kw, src) {
17886
17890
  * fkeys, times and numbers). Route by the form's mode.
17887
17891
  ************************************************************/
17888
17892
  function ac_form_save_record(gobj, event, kw, src) {
17889
- if (refuse_if_readonly(gobj, event)) return -1;
17893
+ if (refuse_if_readonly$1(gobj, event)) return -1;
17890
17894
  let mode = (0, _yuneta_gobj_js.gobj_read_str_attr)(src, "form_mode");
17891
17895
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", {
17892
17896
  topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
@@ -18302,6 +18306,7 @@ var attrs_table$3 = [
18302
18306
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
18303
18307
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
18304
18308
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
18309
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: the graph drops its `edition` mode (the only one that draws write affordances) and refuses every write event. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
18305
18310
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "back_route", 0, "", "Optional hash route for a '← topics' button back to the topics grid (host-supplied; empty = no button)"),
18306
18311
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its per-topic focus sub-routes to the site map (ROUTING.md contributor)."),
18307
18312
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "records", 0, "{}", "Data of topics"),
@@ -18352,6 +18357,7 @@ function mt_create$3(gobj) {
18352
18357
  if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
18353
18358
  (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
18354
18359
  if (!priv.treedb_name) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_name)(gobj)} -> treedb_name not configured`);
18360
+ if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly") && priv.operation_mode === "edition") (0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "operation_mode", "reading");
18355
18361
  let canvas_id = (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj)) + "-canvas";
18356
18362
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "canvas_id", canvas_id);
18357
18363
  let $container_canvas = build_ui$3(gobj).querySelector(`#${priv.canvas_id}`);
@@ -18506,6 +18512,8 @@ function destroy_ui(gobj) {
18506
18512
  ************************************************************/
18507
18513
  function make_toolbar(gobj) {
18508
18514
  gobj.priv;
18515
+ let modes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "operation_modes");
18516
+ if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) modes = modes.filter((mode) => mode !== "edition");
18509
18517
  const $toolbar_header = yui_toolbar({}, [
18510
18518
  [
18511
18519
  "div",
@@ -18544,7 +18552,7 @@ function make_toolbar(gobj) {
18544
18552
  [[
18545
18553
  "select",
18546
18554
  { class: "GRAPH_MODE_SELECT" },
18547
- (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "operation_modes").map((item) => [
18555
+ modes.map((item) => [
18548
18556
  "option",
18549
18557
  {},
18550
18558
  item
@@ -19096,6 +19104,19 @@ function refresh_data(gobj) {
19096
19104
  if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_CLEAR_DATA", {}, gobj);
19097
19105
  request_treedb_descs(gobj);
19098
19106
  }
19107
+ /********************************************
19108
+ * The last gate before a write leaves for the yuno. The `edition`
19109
+ * mode is gone on a replica, so no button can raise these -- but the
19110
+ * G6 child also publishes them from its undo/redo history and from
19111
+ * saving the node geometry, and a write that got this far would
19112
+ * travel, be refused by the yuno, and come back as a toast with the
19113
+ * graph already redrawn.
19114
+ ********************************************/
19115
+ function refuse_if_readonly(gobj, event) {
19116
+ if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) return false;
19117
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, treedb '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}' is READ-ONLY`);
19118
+ return true;
19119
+ }
19099
19120
  /***************************
19100
19121
  * Actions
19101
19122
  ***************************/
@@ -19164,7 +19185,15 @@ function ac_mt_command_answer(gobj, event, kw, src) {
19164
19185
  case "update-node":
19165
19186
  case "delete-node":
19166
19187
  case "link-nodes":
19167
- case "unlink-nodes": break;
19188
+ case "unlink-nodes":
19189
+ if (result >= 0 && (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", {
19190
+ treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
19191
+ topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
19192
+ record: (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0),
19193
+ created: command === "create-node",
19194
+ command
19195
+ });
19196
+ break;
19168
19197
  default: (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)} Command unknown: ${command}`);
19169
19198
  }
19170
19199
  return 0;
@@ -19383,7 +19412,9 @@ function ac_edge_clicked(gobj, event, kw, src) {
19383
19412
  * Send to backend
19384
19413
  ********************************************/
19385
19414
  function ac_create_node(gobj, event, kw, src) {
19386
- let treedb_name = gobj.priv.treedb_name;
19415
+ let priv = gobj.priv;
19416
+ if (refuse_if_readonly(gobj, event)) return -1;
19417
+ let treedb_name = priv.treedb_name;
19387
19418
  let topic_name = kw.topic_name;
19388
19419
  let record = kw.record;
19389
19420
  return treedb_create_node(gobj, treedb_name, topic_name, record, kw.options || {});
@@ -19398,7 +19429,9 @@ function ac_create_node(gobj, event, kw, src) {
19398
19429
  * Send to backend
19399
19430
  ********************************************/
19400
19431
  function ac_update_node(gobj, event, kw, src) {
19401
- let treedb_name = gobj.priv.treedb_name;
19432
+ let priv = gobj.priv;
19433
+ if (refuse_if_readonly(gobj, event)) return -1;
19434
+ let treedb_name = priv.treedb_name;
19402
19435
  let topic_name = kw.topic_name;
19403
19436
  let record = kw.record;
19404
19437
  return treedb_update_node(gobj, treedb_name, topic_name, record, kw.options || {});
@@ -19413,7 +19446,9 @@ function ac_update_node(gobj, event, kw, src) {
19413
19446
  * Send to backend
19414
19447
  ********************************************/
19415
19448
  function ac_delete_node(gobj, event, kw, src) {
19416
- let treedb_name = gobj.priv.treedb_name;
19449
+ let priv = gobj.priv;
19450
+ if (refuse_if_readonly(gobj, event)) return -1;
19451
+ let treedb_name = priv.treedb_name;
19417
19452
  let topic_name = kw.topic_name;
19418
19453
  let record = kw.record;
19419
19454
  return treedb_delete_node(gobj, treedb_name, topic_name, record, kw.options || {});
@@ -19428,7 +19463,9 @@ function ac_delete_node(gobj, event, kw, src) {
19428
19463
  * Send to backend
19429
19464
  ********************************************/
19430
19465
  function ac_link_nodes(gobj, event, kw, src) {
19431
- let treedb_name = gobj.priv.treedb_name;
19466
+ let priv = gobj.priv;
19467
+ if (refuse_if_readonly(gobj, event)) return -1;
19468
+ let treedb_name = priv.treedb_name;
19432
19469
  let parent_ref = kw.parent_ref;
19433
19470
  let child_ref = kw.child_ref;
19434
19471
  return treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, kw.options || {});
@@ -19443,7 +19480,9 @@ function ac_link_nodes(gobj, event, kw, src) {
19443
19480
  * Send to backend
19444
19481
  ********************************************/
19445
19482
  function ac_unlink_nodes(gobj, event, kw, src) {
19446
- let treedb_name = gobj.priv.treedb_name;
19483
+ let priv = gobj.priv;
19484
+ if (refuse_if_readonly(gobj, event)) return -1;
19485
+ let treedb_name = priv.treedb_name;
19447
19486
  let parent_ref = kw.parent_ref;
19448
19487
  let child_ref = kw.child_ref;
19449
19488
  return treedb_unlink_nodes(gobj, treedb_name, parent_ref, child_ref, kw.options || {});
@@ -19483,6 +19522,10 @@ function ac_set_layout$1(gobj, event, kw, src) {
19483
19522
  function ac_set_operation_mode$1(gobj, event, kw, src) {
19484
19523
  let priv = gobj.priv;
19485
19524
  let operation_mode = kw.operation_mode;
19525
+ if (operation_mode === "edition" && (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) {
19526
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: edition mode refused, treedb '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}' is READ-ONLY`);
19527
+ return -1;
19528
+ }
19486
19529
  (0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "operation_mode", operation_mode);
19487
19530
  (0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "operation_mode");
19488
19531
  let $operation_mode_select = priv.$container.querySelector(".GRAPH_MODE_SELECT");
@@ -19704,6 +19747,7 @@ function create_gclass$3(gclass_name) {
19704
19747
  ["EV_SET_OPERATION_MODE", 0],
19705
19748
  ["EV_SET_FOCUS_TOPIC", 0],
19706
19749
  ["EV_OPERATION_MODE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
19750
+ ["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
19707
19751
  ["EV_SHOW", 0],
19708
19752
  ["EV_HIDE", 0],
19709
19753
  ["EV_TRANSPORT_STATE", 0]
@@ -19723,11 +19767,26 @@ function register_c_yui_treedb_graph() {
19723
19767
  /***********************************************************************
19724
19768
  * c_yui_treedb_schema.js
19725
19769
  *
19726
- * Schema-graph landing (prototype): the treedb drawn as a GRAPH OF
19727
- * TOPICS one node per topic, one edge per hook/fkey relationship
19728
- * from the schema `descs` alone (no data, no backend calls). A
19729
- * node click opens that topic's table (a real hash navigation via
19730
- * 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
19731
19790
  * topic cards, in the spirit of "every treedb is a graph".
19732
19791
  *
19733
19792
  * Copyright (c) 2026, ArtGins.
@@ -19737,6 +19796,27 @@ function register_c_yui_treedb_graph() {
19737
19796
  * Constants
19738
19797
  ***************************************************************/
19739
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)";
19740
19820
  /***************************************************************
19741
19821
  * Data
19742
19822
  ***************************************************************/
@@ -19798,48 +19878,254 @@ function build_ui$2(gobj) {
19798
19878
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
19799
19879
  }
19800
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
+ /************************************************************
19801
20031
  * Derive {nodes, edges} from the schema.
19802
- * Node = a topic. Edge = a hook (parent -> child) or an
19803
- * 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
+ *
19804
20041
  * Left-to-right dagre follows the parent -> child data flow.
19805
20042
  ************************************************************/
19806
20043
  function schema_to_graph(gobj) {
19807
20044
  let descs = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "descs");
19808
20045
  let system = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "system");
20046
+ let dark = yui_is_dark();
19809
20047
  let nodes = [];
19810
- let topic_set = {};
20048
+ let cards = {};
19811
20049
  if (!(0, _yuneta_gobj_js.is_object)(descs)) return {
19812
20050
  nodes,
19813
20051
  edges: []
19814
20052
  };
20053
+ let idx = 0;
19815
20054
  for (let topic of Object.keys(descs)) {
19816
- if (!system && topic.substring(0, 2) === "__") continue;
19817
- 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
+ }
19818
20084
  nodes.push({
19819
20085
  id: topic,
19820
- 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
+ }
19821
20102
  });
19822
20103
  }
19823
20104
  let edge_seen = {};
19824
20105
  let edges = [];
19825
- let add_edge = (source, target) => {
19826
- if (!topic_set[source] || !topic_set[target] || source === target) return;
19827
- 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}`;
19828
20109
  if (edge_seen[key]) return;
19829
20110
  edge_seen[key] = true;
19830
20111
  edges.push({
19831
20112
  id: key,
20113
+ type: "cubic",
19832
20114
  source,
19833
- target
20115
+ target,
20116
+ style: {
20117
+ sourcePort: source_port,
20118
+ targetPort: target_port
20119
+ }
19834
20120
  });
19835
20121
  };
19836
- for (let topic of Object.keys(descs)) {
20122
+ for (let topic of Object.keys(cards)) {
19837
20123
  let desc = descs[topic];
19838
20124
  if (!(0, _yuneta_gobj_js.is_object)(desc) || !Array.isArray(desc.cols)) continue;
19839
20125
  for (let col of desc.cols) {
19840
- if (!col) continue;
19841
- if ((0, _yuneta_gobj_js.is_object)(col.hook)) for (let child of Object.keys(col.hook)) add_edge(topic, child);
19842
- 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);
19843
20129
  }
19844
20130
  }
19845
20131
  return {
@@ -19848,26 +20134,15 @@ function schema_to_graph(gobj) {
19848
20134
  };
19849
20135
  }
19850
20136
  /************************************************************
19851
- * The theme-dependent styles, in ONE place: they are applied
19852
- * 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
19853
20139
  * (ac_theme), which restyles the LIVE graph — rebuilding it
19854
20140
  * would drop the user's zoom/pan and any dragged node.
19855
20141
  ************************************************************/
19856
- function node_style(dark) {
19857
- return {
19858
- size: 40,
19859
- fill: "#5B8FF9",
19860
- labelText: (d) => d.id,
19861
- labelPlacement: "bottom",
19862
- labelFill: dark ? "#e6e6e6" : "#333333",
19863
- labelBackground: true,
19864
- labelBackgroundFill: dark ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.7)",
19865
- cursor: "pointer"
19866
- };
19867
- }
19868
20142
  function edge_style(dark) {
19869
20143
  return {
19870
- stroke: dark ? "#666666" : "#bbbbbb",
20144
+ stroke: dark ? "#7a8593" : "#9aa4b2",
20145
+ lineWidth: 1.2,
19871
20146
  endArrow: true
19872
20147
  };
19873
20148
  }
@@ -19899,13 +20174,12 @@ function build_graph$1(gobj) {
19899
20174
  container: $container,
19900
20175
  autoResize: true,
19901
20176
  data,
19902
- node: { style: node_style(dark) },
19903
20177
  edge: { style: edge_style(dark) },
19904
20178
  layout: {
19905
20179
  type: "antv-dagre",
19906
20180
  rankdir: "LR",
19907
- nodesep: 24,
19908
- ranksep: 60
20181
+ nodesep: 28,
20182
+ ranksep: 90
19909
20183
  },
19910
20184
  behaviors: [
19911
20185
  "zoom-canvas",
@@ -19990,8 +20264,17 @@ function ac_theme$1(gobj, event, kw, src) {
19990
20264
  let dark = kw && kw.theme ? kw.theme === "dark" : yui_is_dark();
19991
20265
  try {
19992
20266
  graph.setTheme(dark ? "dark" : "light");
19993
- graph.setNode({ style: node_style(dark) });
19994
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);
19995
20278
  graph.draw().catch((e) => {
19996
20279
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: schema graph redraw failed: ${e}`);
19997
20280
  });
@@ -20056,6 +20339,58 @@ function register_c_yui_treedb_schema() {
20056
20339
  return create_gclass$2(GCLASS_NAME$2);
20057
20340
  }
20058
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
20059
20394
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
20060
20395
  function _typeof(o) {
20061
20396
  "@babel/helpers - typeof";
@@ -47588,19 +47923,19 @@ function create_topic_node(gobj, desc, record) {
47588
47923
  style.size = [116, 40];
47589
47924
  style.dx = -58;
47590
47925
  style.dy = -20;
47591
- 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);
47592
47927
  } else if (node_treedb_type === "extended") {
47593
47928
  node_graph_type = "html";
47594
47929
  style.size = [144, 66];
47595
47930
  style.dx = -72;
47596
47931
  style.dy = -33;
47597
- 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);
47598
47933
  } else {
47599
47934
  node_graph_type = "html";
47600
47935
  style.size = [172, 96];
47601
47936
  style.dx = -86;
47602
47937
  style.dy = -48;
47603
- 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);
47604
47939
  }
47605
47940
  let topic_props = priv._graph_properties[desc.topic_name];
47606
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;
@@ -47668,15 +48003,15 @@ function update_topic_node(gobj, desc, node_name, record) {
47668
48003
  nodedata.data.record = record;
47669
48004
  if (desc.node_treedb_type === "child") graph.updateNodeData([{
47670
48005
  id: node_name,
47671
- 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) }
47672
48007
  }]);
47673
48008
  else if (desc.node_treedb_type === "extended") graph.updateNodeData([{
47674
48009
  id: node_name,
47675
- 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) }
47676
48011
  }]);
47677
48012
  else if (desc.node_treedb_type === "hierarchical") graph.updateNodeData([{
47678
48013
  id: node_name,
47679
- 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) }
47680
48015
  }]);
47681
48016
  } catch (e) {
47682
48017
  (0, _yuneta_gobj_js.log_error)(e.message);
@@ -49583,7 +49918,7 @@ function show_node_popover(gobj) {
49583
49918
  };
49584
49919
  if (node_graph_type === "hierarchical") {
49585
49920
  let record = nodeData.data.record || {};
49586
- 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);
49587
49922
  }
49588
49923
  graph.updateNodeData([{
49589
49924
  id: node_id,
@@ -49623,7 +49958,7 @@ function show_node_popover(gobj) {
49623
49958
  };
49624
49959
  if (node_graph_type === "hierarchical") {
49625
49960
  let record = nodeData.data.record || {};
49626
- 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);
49627
49962
  }
49628
49963
  graph.updateNodeData([{
49629
49964
  id: node_id,
@@ -49724,15 +50059,15 @@ function refresh_html_nodes_theme(gobj, theme) {
49724
50059
  let record = nd.data.record || {};
49725
50060
  if (tt === "hierarchical") updates.push({
49726
50061
  id: nodes[i].id,
49727
- 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) }
49728
50063
  });
49729
50064
  else if (tt === "child") updates.push({
49730
50065
  id: nodes[i].id,
49731
- 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) }
49732
50067
  });
49733
50068
  else if (tt === "extended") updates.push({
49734
50069
  id: nodes[i].id,
49735
- 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) }
49736
50071
  });
49737
50072
  }
49738
50073
  if (updates.length > 0) graph.updateNodeData(updates);
@@ -49764,7 +50099,8 @@ function refresh_default_edges_theme(gobj, theme) {
49764
50099
  * but lighter (1px border, no shadow, single line). The name is
49765
50100
  * always legible (ellipsis + native title tooltip on overflow).
49766
50101
  ************************************************************/
49767
- function build_chip_innerHTML(color, theme, icon, id) {
50102
+ function build_chip_innerHTML(color, theme, icon, label, key) {
50103
+ let title = key || label;
49768
50104
  let dark = theme === "dark";
49769
50105
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
49770
50106
  let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
@@ -49775,7 +50111,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49775
50111
  margin-right: 6px; flex: 0 0 auto;
49776
50112
  "/>`;
49777
50113
  return `
49778
- <div title="${(0, _yuneta_gobj_js.escapeHtml)(id)}" style="
50114
+ <div title="${(0, _yuneta_gobj_js.escapeHtml)(title)}" style="
49779
50115
  box-sizing: border-box;
49780
50116
  width: 100%;
49781
50117
  height: 100%;
@@ -49792,7 +50128,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49792
50128
  ">${icon_html}<span style="
49793
50129
  font-size: 12px; font-weight: 600; line-height: 1;
49794
50130
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49795
- ">${(0, _yuneta_gobj_js.escapeHtml)(id)}</span>
50131
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>
49796
50132
  </div>
49797
50133
  `;
49798
50134
  }
@@ -49806,7 +50142,8 @@ function build_chip_innerHTML(color, theme, icon, id) {
49806
50142
  * colour is kept (per-topic differentiation) but softened via
49807
50143
  * color-mix instead of a harsh saturated fill. Theme-aware.
49808
50144
  ************************************************************/
49809
- 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;
49810
50147
  let dark = theme === "dark";
49811
50148
  let surface = dark ? "#1b2230" : "#ffffff";
49812
50149
  let bg, border, border_style;
@@ -49836,7 +50173,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49836
50173
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49837
50174
  ">${(0, _yuneta_gobj_js.escapeHtml)(topic_name)}</div>`;
49838
50175
  return `
49839
- <div title="${(0, _yuneta_gobj_js.escapeHtml)(id)}" style="
50176
+ <div title="${(0, _yuneta_gobj_js.escapeHtml)(title)}" style="
49840
50177
  box-sizing: border-box;
49841
50178
  width: 100%;
49842
50179
  height: 100%;
@@ -49859,7 +50196,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49859
50196
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
49860
50197
  display: -webkit-box; -webkit-line-clamp: 2;
49861
50198
  -webkit-box-orient: vertical; word-break: break-word;
49862
- ">${(0, _yuneta_gobj_js.escapeHtml)(id)}</div>${sub_html}
50199
+ ">${(0, _yuneta_gobj_js.escapeHtml)(label)}</div>${sub_html}
49863
50200
  </div>
49864
50201
  `;
49865
50202
  }
@@ -50080,7 +50417,7 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
50080
50417
  };
50081
50418
  if (nd.data.desc.node_treedb_type === "hierarchical") {
50082
50419
  let record = nd.data.record || {};
50083
- 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);
50084
50421
  }
50085
50422
  updates.push({
50086
50423
  id: nodes[i].id,