@yuneta/gobj-ui 7.25.18 → 7.25.20

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.
package/README.md CHANGED
@@ -227,6 +227,16 @@ seen*:
227
227
  for its treedb nodes, so the graph tab returns to `<graph>/<topic>` with its
228
228
  focus and the data tab to the topic that was open.
229
229
 
230
+ - **A node hears every event of the navs it projects** (they are its pure
231
+ children). Since `7.25.19` it declares all three, in every state -- a ZONE
232
+ projection outlives the path, so a root that is off it still hears its
233
+ rail: `EV_NAV_CLICKED` (a navigation), `EV_NAV_ITEM_CLOSE` (handed to the
234
+ shell, which re-publishes it to the app, as for the shell's own closable
235
+ items) and `EV_DRAWER_CLOSE_REQUESTED` (the node closes its own drawer
236
+ projection: the shell's `yui_shell_close_drawer()` knows only the shell's
237
+ navs). Before, the last two answered *"Event NOT DEFINED in state"*, and so
238
+ did a click on a zone nav of a root in `ST_OFF`.
239
+
230
240
  - **`nav_mode` — the three shapes as one runtime knob.** The two bullets above
231
241
  describe what a tree *declares*; `nav_mode` is how a user *chooses* between
232
242
  the shapes without the app rewriting anything:
@@ -576,13 +586,33 @@ Two layout facts the browser taught this component, both worth keeping:
576
586
  `"graph"`), `$container` (mounted by the parent).
577
587
  - Input events: `EV_SET_JSON {json}` (replace the whole document; `ST_EMPTY` →
578
588
  `ST_READY`), `EV_SUBTREE_LOADED {path, json}` (splice a fetched subtree),
579
- `EV_SUBTREE_ERROR {path, error}`, `EV_SET_VIEW_MODE {mode}` (`"tree"` /
589
+ `EV_SUBTREE_ERROR {path, error | i18n, by_design}`, `EV_SET_VIEW_MODE {mode}` (`"tree"` /
580
590
  `"text"` / `"graph"`; **no mode advances** to the next view, which is what
581
591
  the two-view toggle did when the list was two long), plus `EV_REFRESH` /
582
592
  `EV_SHOW` / `EV_HIDE` / `EV_LANGUAGE_CHANGED`.
583
593
  - Output event: `EV_EXPAND_PATH {path, size}` (`EVF_OUTPUT_EVENT`) — the parent
584
- must declare it in its own FSM (CHILD subscription model). That is the ONLY
585
- one, on purpose: this viewer is a child of its host and subscribes it to
594
+ must declare it in its own FSM (CHILD subscription model), and ANSWER it: the
595
+ stub shows "loading" until `EV_SUBTREE_LOADED` or `EV_SUBTREE_ERROR` arrives.
596
+ A host that cannot read a subtree says so with the error, as
597
+ `C_YUI_TREEDB_TOPIC_WITH_FORM` does for its schema, cell and table viewers
598
+ since `7.25.19` (before, it declared nothing: *"Event NOT DEFINED"*):
599
+ ```js
600
+ gobj_send_event(src, "EV_SUBTREE_ERROR", {
601
+ path: kw.path,
602
+ i18n: "this part cannot be loaded here", // a KEY: t(key) + data-i18n
603
+ by_design: true // not a failure: logged as a warning
604
+ }, gobj);
605
+ ```
606
+ `error` is text, drawn as it came (a backend's comment); `i18n` is a key,
607
+ so the stub changes language with the app -- a text translated by the host
608
+ stays in the language it was sent in. The name `i18n` is the one a
609
+ `validate-locales` scan looks for, so the key stays visible to it. `by_design`
610
+ says the host CANNOT, which is not a failure: the viewer logs it as a
611
+ WARNING, and anything else as an ERROR. Before `7.25.20` the form sent
612
+ `t(...)`, and every click on a stub logged an ERROR, which broke a
613
+ clean-console check and reached the backend through the remote log.
614
+
615
+ `EV_EXPAND_PATH` is the ONLY output event, on purpose: this viewer is a child of its host and subscribes it to
586
616
  everything it publishes, so every output event is a mandatory declaration in
587
617
  every host's FSM. The graph child's `EV_JSON_ITEM_CLICKED` stops here
588
618
  (`7.21.0` forwarded it and broke exactly that way); a host that wants node
@@ -1003,7 +1033,7 @@ graph does not report `__graphs__`, its own bookkeeping.
1003
1033
  treedb_name: "treedb_test", // the view's own treedb
1004
1034
  topic_name: "yunos", // the topic written (the CHILD's, for a link)
1005
1035
  record: {id: "1", x: 1, y: 2}, // the node the store answered, as written
1006
- created: false, // true for create-node
1036
+ created: false, // true for a NEW record (see below)
1007
1037
  command: "update-node", // create/update/delete-node, link/unlink-nodes
1008
1038
  parent_ref: "realms^r1^yunos", // graph, link/unlink only
1009
1039
  child_ref: "yunos^1" // graph, link/unlink only
@@ -1018,6 +1048,21 @@ do -- so `treedb_name` and `record` arrived empty, and a link named no topic.
1018
1048
  delete answers the node deleted, a link the child), and a link echoes its topic
1019
1049
  and refs in `__md_command__`.
1020
1050
 
1051
+ Since `7.25.19` the topics view publishes a successful **delete** too (it
1052
+ published nothing: the answer went to an empty `break`), and `created` is
1053
+ TRUE for its **+New**: that goes out as an `update-node` with `create_only`,
1054
+ so comparing the command with `"create-node"` answered `false` for every
1055
+ record the table created. The request now echoes `created: true` in its
1056
+ `__md_command__`. The graph creates with `create-node`, and says so the same
1057
+ way.
1058
+
1059
+ ```js
1060
+ // the table's +New, answered
1061
+ {treedb_name: "treedb_test", topic_name: "users", record: {id: "n1"}, created: true, command: "update-node"}
1062
+ // a row deleted, answered with the node deleted
1063
+ {treedb_name: "treedb_test", topic_name: "roles", record: {id: "x"}, created: false, command: "delete-node"}
1064
+ ```
1065
+
1021
1066
  ### Read-only treedbs: `readonly`
1022
1067
 
1023
1068
  `C_YUI_TREEDB_TOPICS` and `C_YUI_TREEDB_GRAPH` take a **`readonly`** attr; the
@@ -1844,8 +1889,12 @@ the history buttons -- they decide the Save from `history.canUndo()`, and they
1844
1889
  run on every history change, every change of mode and every redraw of the
1845
1890
  theme -- and a refusal heard in reading was never shown. The engine now keeps
1846
1891
  the topic as owed: the Save stays lit while one is, in edition and on entering
1847
- it; the Save that writes the topic again pays it, and a reload of the data
1848
- forgets it (the view holds what the backend holds).
1892
+ it; the Save that writes the topic again pays it, a reload of the data
1893
+ forgets it (the view holds what the backend holds), and so does an ECHO of the
1894
+ topic's `__graphs__` record (since `7.25.19`): the echo is what the backend
1895
+ holds, taken as shown and as saved, so nothing is owed any more. Before, a
1896
+ refused Save #1 answered after Save #2's echo -- or another browser saving the
1897
+ topic -- left the Save lit with nothing to write until a reload.
1849
1898
 
1850
1899
  ```js
1851
1900
  gobj_send_event(engine, "EV_GRAPHS_WRITE_REFUSED", {topic: "users"}, host); // Save lit
@@ -1863,10 +1912,34 @@ worked only in a test whose fake transport echoed the whole request. The host
1863
1912
  now puts the topic in the echo it asks for:
1864
1913
 
1865
1914
  ```js
1866
- kw.__md_command__ = {topic_name: "__graphs__", graph_topic: "users"};
1915
+ kw.__md_command__ = {topic_name: "__graphs__", graph_topic: "users", graphs_load: 3};
1867
1916
  // the answer: __md_iev__.command_stack[0].kw === that echo, and nothing else
1868
1917
  ```
1869
1918
 
1919
+ **A refusal belongs to the LOAD its write was sent from** (since `7.25.20`).
1920
+ "A reload forgets it" held only for a refusal that arrived BEFORE the reload:
1921
+ one answered after it was applied to the fresh load -- Save, Refresh, then the
1922
+ refusal lands -- and lit Save over nothing changed, and pressing it wrote a
1923
+ record identical to what the backend holds. The engine now counts its loads
1924
+ (each `EV_CLEAR_DATA` starts one) and puts the number in every `EV_UPDATE_NODE`
1925
+ of `__graphs__` as `graphs_load`; the host echoes it in `__md_command__` and
1926
+ hands it back with `EV_GRAPHS_WRITE_REFUSED {topic, graphs_load}`. A refusal of
1927
+ an earlier load is logged as a warning and ignored. A host that sends no
1928
+ `graphs_load` is taken at its word, as before:
1929
+
1930
+ ```js
1931
+ // in the host: the engine's load, from the EV_UPDATE_NODE it published
1932
+ gobj_send_event(engine, "EV_GRAPHS_WRITE_REFUSED",
1933
+ {topic: record.topic, graphs_load: kw.graphs_load}, gobj);
1934
+ ```
1935
+
1936
+ **A `__graphs__` create echo moves only its own topic** (since `7.25.20`), as
1937
+ an update echo has since `7.25.19` (`apply_graphs_echo()`). The first record of
1938
+ a topic's arrangement -- from this browser or another -- rebuilt the saved
1939
+ copies of EVERY topic from the live objects the view arranges in place, so an
1940
+ unsaved change of another topic (a look reset in edition, say) counted as saved
1941
+ and the next Save did not write it.
1942
+
1870
1943
  **Every saved look has a way back, in the context menu** (since `7.23.134`,
1871
1944
  replacing `reset sizes` / `reset topic sizes`). In edition, the node, the port
1872
1945
  and the edge menus each offer three resets — this one, its kind (`reset topic
@@ -2344,7 +2417,13 @@ else.
2344
2417
  consumer's locales carry). A write that ends with an error -- a drop
2345
2418
  included -- still applies a position that waited, as a load does. Out of
2346
2419
  session a load is not asked at all (no *"not in session"* errors); the
2347
- reconnect loads.
2420
+ reconnect loads. **Nor is a write** (7.25.20): each one of the queue asks
2421
+ first, and out of session the writes end with `cannot reach the treedb`, a
2422
+ warning, and a reload owed to the next session. A direct `C_IEVENT_CLI`
2423
+ answers a command out of session with `null`, the answer of a request that
2424
+ left, so before this the editor sat in `ST_SAVING`, inert, until another
2425
+ drop. (gui_agent's `C_AGENT_TREEDB_LINK` answers an error string there, so
2426
+ it was not hit.)
2348
2427
  - **A dialog is built on ONE model, and a load that replaces it closes it**
2349
2428
  (7.25.9). Every load that lands bumps `model_gen`; a dialog records the one
2350
2429
  it was opened on, and its Save, its import Preview/Run, an orphan delete and
@@ -10901,7 +10901,7 @@ function register_c_yui_map() {
10901
10901
  *
10902
10902
  * Graphics, Charts with uPlot
10903
10903
  *
10904
- * Copyright (c) 2025, ArtGins.
10904
+ * Copyright (c) 2025-2026, ArtGins.
10905
10905
  * All Rights Reserved.
10906
10906
  ***********************************************************************/
10907
10907
  /***************************************************************
@@ -11151,8 +11151,8 @@ function ac_add_serie(gobj, event, kw, src) {
11151
11151
  let colors = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "colors");
11152
11152
  if (!Array.isArray(colors) || !colors.length) colors = CHART_PALETTE;
11153
11153
  let color = colors[Math.max(0, idx - 1) % colors.length];
11154
- (0, _yuneta_gobj_js.kw_get_str)(gobj, kw, "stroke", color.stroke, _yuneta_gobj_js.kw_flag_t.KW_CREATE);
11155
- (0, _yuneta_gobj_js.kw_get_str)(gobj, kw, "fill", color.fill, _yuneta_gobj_js.kw_flag_t.KW_CREATE);
11154
+ if (kw.stroke === void 0) kw.stroke = color.stroke;
11155
+ if (kw.fill === void 0) kw.fill = color.fill;
11156
11156
  uplot$4.addSeries(kw);
11157
11157
  return 0;
11158
11158
  }
@@ -14743,10 +14743,18 @@ function push_collapsed_row(ctx, value, segments, depth, key, rows) {
14743
14743
  },
14744
14744
  is_pending ? "loading" : "click to load"
14745
14745
  ]);
14746
- if (err) children.push([
14746
+ if (err && err.is_key) children.push([
14747
+ "span",
14748
+ {
14749
+ class: "JSON_STUB_ERR has-text-danger is-size-7 ml-2",
14750
+ "data-i18n": err.error
14751
+ },
14752
+ (0, i18next.t)(err.error)
14753
+ ]);
14754
+ else if (err) children.push([
14747
14755
  "span",
14748
14756
  { class: "JSON_STUB_ERR has-text-danger is-size-7 ml-2" },
14749
- String(err)
14757
+ String(err.error)
14750
14758
  ]);
14751
14759
  let attrs = {
14752
14760
  class: "JSON_ROW JSON_COLLAPSED" + (is_pending ? " is-pending" : ""),
@@ -14963,14 +14971,32 @@ function ac_subtree_loaded(gobj, event, kw, src) {
14963
14971
  return 0;
14964
14972
  }
14965
14973
  /************************************************************
14966
- * EV_SUBTREE_ERROR { path, error } — mark the failed branch
14974
+ * EV_SUBTREE_ERROR { path, error | i18n, by_design }
14975
+ * — mark the branch that was not loaded.
14976
+ *
14977
+ * `error` is text, drawn as it came (a backend's comment).
14978
+ * `i18n` is a KEY, drawn as t(key) with data-i18n so the stub
14979
+ * changes language with the app (a text translated by the host
14980
+ * stays in the language it was sent in). The name is the one a
14981
+ * locale check scans for (`i18n: "<key>"`), so the key of every
14982
+ * host stays visible to it.
14983
+ * `by_design`: the host answers that it CANNOT read a subtree
14984
+ * (a form holds records, a pasted pad has no backend) -- that is
14985
+ * not a failure, and it is logged as a warning. Anything else is
14986
+ * a load that failed, logged as an error.
14967
14987
  ************************************************************/
14968
14988
  function ac_subtree_error(gobj, event, kw, src) {
14969
14989
  let priv = gobj.priv;
14970
14990
  let path = kw.path || "";
14991
+ let is_key = !!kw.i18n;
14992
+ let error = kw.i18n || kw.error || "error";
14971
14993
  priv.pending.delete(path);
14972
- priv.errors.set(path, kw.error || "error");
14973
- (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$13}: subtree load failed at '${path}': ${kw.error || ""}`);
14994
+ priv.errors.set(path, {
14995
+ error,
14996
+ is_key
14997
+ });
14998
+ if (kw.by_design) (0, _yuneta_gobj_js.log_warning)(`${GCLASS_NAME$13}: subtree not loaded at '${path}': ${error}`);
14999
+ else (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$13}: subtree load failed at '${path}': ${error}`);
14974
15000
  render_view$1(gobj);
14975
15001
  return 0;
14976
15002
  }
@@ -16020,10 +16046,11 @@ function ac_hide_diff(gobj, event, kw, src) {
16020
16046
  * document has no backend to ask for it: the SOURCE truncated
16021
16047
  * it, and that is the answer, to the viewer that asked.
16022
16048
  ***************************************************************/
16023
- function ac_expand_path(gobj, event, kw, src) {
16049
+ function ac_expand_path$1(gobj, event, kw, src) {
16024
16050
  (0, _yuneta_gobj_js.gobj_send_event)(src, "EV_SUBTREE_ERROR", {
16025
16051
  path: kw && kw.path || "",
16026
- error: (0, i18next.t)("collapsed in the source")
16052
+ i18n: "collapsed in the source",
16053
+ by_design: true
16027
16054
  }, gobj);
16028
16055
  return 0;
16029
16056
  }
@@ -16081,7 +16108,7 @@ function create_gclass$12(gclass_name) {
16081
16108
  ],
16082
16109
  [
16083
16110
  "EV_EXPAND_PATH",
16084
- ac_expand_path,
16111
+ ac_expand_path$1,
16085
16112
  null
16086
16113
  ]
16087
16114
  ]],
@@ -16118,7 +16145,7 @@ function create_gclass$12(gclass_name) {
16118
16145
  ],
16119
16146
  [
16120
16147
  "EV_EXPAND_PATH",
16121
- ac_expand_path,
16148
+ ac_expand_path$1,
16122
16149
  null
16123
16150
  ]
16124
16151
  ]],
@@ -24937,7 +24964,7 @@ function answered_record$1(data) {
24937
24964
  /************************************************************
24938
24965
  * Command to remote service
24939
24966
  ************************************************************/
24940
- function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write) {
24967
+ function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write, created) {
24941
24968
  let command = "update-node";
24942
24969
  let gobj_remote_yuno = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno");
24943
24970
  let kw = {
@@ -24949,6 +24976,7 @@ function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, fo
24949
24976
  };
24950
24977
  kw.__md_command__ = { topic_name };
24951
24978
  if (form_write) kw.__md_command__.form_write = form_write;
24979
+ if (created) kw.__md_command__.created = true;
24952
24980
  let ret = (0, _yuneta_gobj_js.gobj_command)(gobj_remote_yuno, command, kw, gobj);
24953
24981
  if (ret) {
24954
24982
  (0, _yuneta_gobj_js.log_error)(ret);
@@ -24972,7 +25000,11 @@ function treedb_delete_node$1(gobj, treedb_name, topic_name, record, options) {
24972
25000
  };
24973
25001
  kw.__md_command__ = { topic_name };
24974
25002
  let ret = (0, _yuneta_gobj_js.gobj_command)(gobj_remote_yuno, command, kw, gobj);
24975
- if (ret) (0, _yuneta_gobj_js.log_error)(ret);
25003
+ if (ret) {
25004
+ (0, _yuneta_gobj_js.log_error)(ret);
25005
+ return -1;
25006
+ }
25007
+ return 0;
24976
25008
  }
24977
25009
  /************************************************************
24978
25010
  * True on a phone-width viewport (Bulma's mobile breakpoint).
@@ -25114,7 +25146,7 @@ function request_print_tranger$1(gobj, path) {
25114
25146
  let jv = priv.json_gobj;
25115
25147
  if (path && jv && (0, _yuneta_gobj_js.is_gobj)(jv) && !(0, _yuneta_gobj_js.gobj_is_destroying)(jv)) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
25116
25148
  path,
25117
- error: (0, i18next.t)("no session")
25149
+ i18n: "no session"
25118
25150
  }, gobj);
25119
25151
  return;
25120
25152
  }
@@ -25255,17 +25287,18 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25255
25287
  break;
25256
25288
  case "create-node":
25257
25289
  case "update-node":
25290
+ case "delete-node":
25258
25291
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", {
25259
25292
  treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name"),
25260
25293
  topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
25261
25294
  record: answered_record$1(data),
25262
- created: command === "create-node",
25295
+ created: command === "create-node" || kw_command.created === true,
25263
25296
  command
25264
25297
  });
25298
+ if (command === "delete-node") break;
25265
25299
  settle_form_write(gobj.priv.form_writes, (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0));
25266
25300
  answer_form_write(gobj, get_gobj_formtable(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0)), (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), true);
25267
25301
  break;
25268
- case "delete-node": break;
25269
25302
  default: (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)} Command unknown: ${command}`);
25270
25303
  }
25271
25304
  return 0;
@@ -25533,7 +25566,7 @@ function ac_create_record(gobj, event, kw, src) {
25533
25566
  create: true,
25534
25567
  create_only: true,
25535
25568
  autolink: true
25536
- }, form_write) < 0) {
25569
+ }, form_write, true) < 0) {
25537
25570
  answer_form_write(gobj, src, form_write, false);
25538
25571
  return -1;
25539
25572
  }
@@ -29451,6 +29484,25 @@ function refuse_if_readonly$2(gobj, event) {
29451
29484
  * From external, at the beginning, load all topic data
29452
29485
  ************************************************************/
29453
29486
  /***************************************************************
29487
+ * One of MY json viewers (the schema, a cell, the table's records)
29488
+ * met a `__collapsed__` sentinel and the reader opened it. The
29489
+ * viewer never fetches: it asks its host, and waits on "loading"
29490
+ * until it is answered. This view holds records, not a store it can
29491
+ * read a subtree of -- the lazy drill is `print-tranger`, which the
29492
+ * TOPICS view runs for its own viewer, over a whole tranger -- so the
29493
+ * answer is that it cannot, and the viewer shows that on the stub
29494
+ * instead of loading for ever.
29495
+ ***************************************************************/
29496
+ function ac_expand_path(gobj, event, kw, src) {
29497
+ let path = kw && kw.path || "";
29498
+ (0, _yuneta_gobj_js.gobj_send_event)(src, "EV_SUBTREE_ERROR", {
29499
+ path,
29500
+ i18n: "this part cannot be loaded here",
29501
+ by_design: true
29502
+ }, gobj);
29503
+ return 0;
29504
+ }
29505
+ /***************************************************************
29454
29506
  * The language changed (the shell publishes it after the app switched).
29455
29507
  *
29456
29508
  * The column headers carry their key (col_label's title formatter emits a
@@ -30744,6 +30796,11 @@ function create_gclass$5(gclass_name) {
30744
30796
  ac_show_schema,
30745
30797
  null
30746
30798
  ],
30799
+ [
30800
+ "EV_EXPAND_PATH",
30801
+ ac_expand_path,
30802
+ null
30803
+ ],
30747
30804
  [
30748
30805
  "EV_CELL_EDITED",
30749
30806
  ac_cell_edited,
@@ -30859,6 +30916,7 @@ function create_gclass$5(gclass_name) {
30859
30916
  ["EV_CHANGE_LOCALE", 0],
30860
30917
  ["EV_REFRESH", 0],
30861
30918
  ["EV_SHOW_SCHEMA", 0],
30919
+ ["EV_EXPAND_PATH", 0],
30862
30920
  ["EV_CELL_EDITED", 0],
30863
30921
  ["EV_REPULL_PAGE", 0],
30864
30922
  ["EV_PAGE_LOADED", 0],
@@ -59264,6 +59322,7 @@ var PRIVATE_DATA$4 = {
59264
59322
  _graph_properties: {},
59265
59323
  _saved_graph_properties: {},
59266
59324
  _graphs_writes_owed: {},
59325
+ _graphs_load: 0,
59267
59326
  yet_showed: false,
59268
59327
  edit_mode: false,
59269
59328
  operation_mode: null,
@@ -60973,7 +61032,8 @@ function save_topic_graph_properties(gobj, topic_name) {
60973
61032
  list_dict: true,
60974
61033
  autolink: false,
60975
61034
  create: true
60976
- }
61035
+ },
61036
+ graphs_load: priv._graphs_load
60977
61037
  };
60978
61038
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_UPDATE_NODE", kw_update);
60979
61039
  }
@@ -65754,6 +65814,7 @@ function ac_clear_data(gobj, event, kw, src) {
65754
65814
  hide_create_popover(gobj);
65755
65815
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "records", {});
65756
65816
  priv._graphs_writes_owed = {};
65817
+ priv._graphs_load++;
65757
65818
  priv._nodes_total = 0;
65758
65819
  priv._nodes_placed = 0;
65759
65820
  priv._fold_model = null;
@@ -65855,12 +65916,19 @@ function ac_save_graph(gobj, event, kw, src) {
65855
65916
  /************************************************************
65856
65917
  * The host says a write of `__graphs__` did not land: the
65857
65918
  * backend refused it, the transport refused it on the way out,
65858
- * or there was no session to send it on. kw: {topic}
65919
+ * or there was no session to send it on.
65920
+ * kw: {topic, graphs_load}
65859
65921
  *
65860
65922
  * save_topic_graph_properties() recorded it as saved before it
65861
65923
  * left. Forgotten now, and the Save is lit again: the next Save
65862
65924
  * writes the topic again. Before, the view believed the backend
65863
65925
  * held the arrangement, and the next Save had nothing to write.
65926
+ *
65927
+ * `graphs_load` is the load the write was sent from, echoed by the
65928
+ * host. A refusal of an EARLIER load is about an arrangement the
65929
+ * reload replaced with what the backend holds: applied, it lit the
65930
+ * Save over nothing changed (before gobj-ui 7.25.20). A host that
65931
+ * echoes no load is taken at its word, as before.
65864
65932
  ************************************************************/
65865
65933
  function ac_graphs_write_refused(gobj, event, kw, src) {
65866
65934
  let priv = gobj.priv;
@@ -65869,6 +65937,10 @@ function ac_graphs_write_refused(gobj, event, kw, src) {
65869
65937
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no topic`);
65870
65938
  return -1;
65871
65939
  }
65940
+ if (kw.graphs_load !== void 0 && kw.graphs_load !== priv._graphs_load) {
65941
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: a refused write of the arrangement of '${topic_name}' belongs to an earlier load: ignored`);
65942
+ return 0;
65943
+ }
65872
65944
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the arrangement of '${topic_name}' was not saved: the next Save writes it again`);
65873
65945
  forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
65874
65946
  priv._graphs_writes_owed[topic_name] = true;
@@ -65876,6 +65948,22 @@ function ac_graphs_write_refused(gobj, event, kw, src) {
65876
65948
  return 0;
65877
65949
  }
65878
65950
  /************************************************************
65951
+ * An echo of a `__graphs__` record is what the backend holds for
65952
+ * its topic now, and the view has just taken it as shown and as
65953
+ * saved: a write of that topic owed from an earlier refusal is
65954
+ * owed no more. Left owed, the Save stayed lit with nothing to
65955
+ * write -- the plan compares the two, and they are equal -- until
65956
+ * a reload: a refused Save #1 answered after Save #2 landed, or
65957
+ * another browser saving the topic, did exactly that.
65958
+ ************************************************************/
65959
+ function settle_owed_graphs_write(gobj, rec) {
65960
+ let priv = gobj.priv;
65961
+ let topic_name = rec && rec.topic;
65962
+ if (!topic_name || !priv._graphs_writes_owed[topic_name]) return;
65963
+ delete priv._graphs_writes_owed[topic_name];
65964
+ update_history_buttons(gobj);
65965
+ }
65966
+ /************************************************************
65879
65967
  * Node created, from subscription
65880
65968
  ************************************************************/
65881
65969
  function ac_node_created(gobj, event, kw, src) {
@@ -65886,7 +65974,8 @@ function ac_node_created(gobj, event, kw, src) {
65886
65974
  if (!priv.descs || !priv.graph) return 0;
65887
65975
  if (topic_name === "__graphs__") {
65888
65976
  priv.__graphs__.push(node);
65889
- build_graph_properties(gobj);
65977
+ apply_graphs_echo(priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node);
65978
+ settle_owed_graphs_write(gobj, node);
65890
65979
  return 0;
65891
65980
  }
65892
65981
  if (!priv.descs[topic_name]) {
@@ -65922,6 +66011,7 @@ function ac_node_updated(gobj, event, kw, src) {
65922
66011
  }
65923
66012
  if (!found) priv.__graphs__.push(node);
65924
66013
  apply_graphs_echo(priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node);
66014
+ settle_owed_graphs_write(gobj, node);
65925
66015
  return 0;
65926
66016
  }
65927
66017
  let desc = priv.descs[topic_name];
@@ -68265,7 +68355,7 @@ function request_print_tranger(gobj, path) {
68265
68355
  let jv = priv.json_gobj;
68266
68356
  if (path && jv && (0, _yuneta_gobj_js.is_gobj)(jv) && !(0, _yuneta_gobj_js.gobj_is_destroying)(jv)) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
68267
68357
  path,
68268
- error: (0, i18next.t)("no session")
68358
+ i18n: "no session"
68269
68359
  }, gobj);
68270
68360
  return;
68271
68361
  }
@@ -68374,7 +68464,7 @@ function treedb_create_node(gobj, treedb_name, topic_name, record, options) {
68374
68464
  /************************************************************
68375
68465
  * Command to remote service
68376
68466
  ************************************************************/
68377
- function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68467
+ function treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load) {
68378
68468
  let priv = gobj.priv;
68379
68469
  if (!priv.gobj_remote_yuno) {
68380
68470
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: No gobj_remote_yuno defined`);
@@ -68389,7 +68479,10 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68389
68479
  options: options || {}
68390
68480
  };
68391
68481
  kw.__md_command__ = { topic_name };
68392
- if (topic_name === "__graphs__") kw.__md_command__.graph_topic = graph_topic_of(record);
68482
+ if (topic_name === "__graphs__") {
68483
+ kw.__md_command__.graph_topic = graph_topic_of(record);
68484
+ if (graphs_load !== void 0) kw.__md_command__.graphs_load = graphs_load;
68485
+ }
68393
68486
  let ret = (0, _yuneta_gobj_js.gobj_command)(priv.gobj_remote_yuno, command, kw, gobj);
68394
68487
  if (ret) {
68395
68488
  (0, _yuneta_gobj_js.log_error)(ret);
@@ -68693,7 +68786,7 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68693
68786
  case "delete-node":
68694
68787
  case "link-nodes":
68695
68788
  case "unlink-nodes":
68696
- if (result < 0 && command === "update-node") graphs_write_refused(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0), (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "graph_topic", "", 0));
68789
+ if (result < 0 && command === "update-node") graphs_write_refused(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0), (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "graph_topic", "", 0), kw_command ? kw_command.graphs_load : void 0);
68697
68790
  if (result >= 0 && (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
68698
68791
  let written = {
68699
68792
  treedb_name: priv.treedb_name,
@@ -68948,17 +69041,18 @@ function ac_update_node(gobj, event, kw, src) {
68948
69041
  let topic_name = kw.topic_name;
68949
69042
  let record = kw.record;
68950
69043
  let options = kw.options || {};
69044
+ let graphs_load = kw.graphs_load;
68951
69045
  if (refuse_if_readonly$1(gobj, event)) {
68952
- graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69046
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68953
69047
  return -1;
68954
69048
  }
68955
69049
  if (topic_name === "__graphs__" && !is_connected(gobj)) {
68956
69050
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no session, the arrangement was not sent`);
68957
- graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69051
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68958
69052
  return -1;
68959
69053
  }
68960
- let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
68961
- if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69054
+ let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load);
69055
+ if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68962
69056
  return ret;
68963
69057
  }
68964
69058
  /************************************************************
@@ -68973,9 +69067,11 @@ function graph_topic_of(record) {
68973
69067
  * (C_G6_NODES_TREE) took it for granted when it left -- nothing
68974
69068
  * answers it when it lands -- so it is told, and its next Save
68975
69069
  * writes the topic again. Other topics are the operator's data:
68976
- * the answer's error says what happened to them.
69070
+ * the answer's error says what happened to them. `graphs_load` is
69071
+ * the engine's load the write was sent from, handed back so it can
69072
+ * tell a refusal of an earlier load from one of the current.
68977
69073
  ************************************************************/
68978
- function graphs_write_refused(gobj, topic_name, topic) {
69074
+ function graphs_write_refused(gobj, topic_name, topic, graphs_load) {
68979
69075
  let priv = gobj.priv;
68980
69076
  if (topic_name !== "__graphs__") return;
68981
69077
  if (!topic) {
@@ -68983,7 +69079,9 @@ function graphs_write_refused(gobj, topic_name, topic) {
68983
69079
  return;
68984
69080
  }
68985
69081
  if (!priv.gobj_nodes_tree) return;
68986
- (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", { topic }, gobj);
69082
+ let kw = { topic };
69083
+ if (graphs_load !== void 0) kw.graphs_load = graphs_load;
69084
+ (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", kw, gobj);
68987
69085
  }
68988
69086
  /********************************************
68989
69087
  * Message from G6_nodes_tree
@@ -72956,8 +73054,12 @@ function current_topic(gobj) {
72956
73054
  * One line for what the whole view has to say: loading, empty,
72957
73055
  * or the reason nothing loaded. Carries its i18n key, so it
72958
73056
  * follows a language change like any other text.
73057
+ *
73058
+ * `detail` is a NAME (a treedb, a topic) shown as it is, unless
73059
+ * `detail_is_key` says it is a key: a name is not text to
73060
+ * translate, and a topic called `nodes` is not "Nodos".
72959
73061
  ***************************************************************/
72960
- function show_notice(gobj, key, detail) {
73062
+ function show_notice(gobj, key, detail, detail_is_key) {
72961
73063
  let $notice = $of(gobj, ".SCHEMA_NOTICE");
72962
73064
  if (!$notice) return;
72963
73065
  clear($notice);
@@ -72974,14 +73076,15 @@ function show_notice(gobj, key, detail) {
72974
73076
  },
72975
73077
  (0, i18next.t)(key)
72976
73078
  ]));
72977
- if (detail && detail !== key) $notice.appendChild((0, _yuneta_gobj_js.createElement2)([
72978
- "span",
72979
- {
72980
- class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2",
72981
- i18n: `${detail}`
72982
- },
72983
- (0, i18next.t)(`${detail}`)
72984
- ]));
73079
+ if (detail && detail !== key) {
73080
+ let attrs = { class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2" };
73081
+ if (detail_is_key) attrs.i18n = `${detail}`;
73082
+ $notice.appendChild((0, _yuneta_gobj_js.createElement2)([
73083
+ "span",
73084
+ attrs,
73085
+ detail_is_key ? (0, i18next.t)(`${detail}`) : `${detail}`
73086
+ ]));
73087
+ }
72985
73088
  }
72986
73089
  /***************************************************************
72987
73090
  * A button of the toolbar. Every one carries an icon and a
@@ -73168,7 +73271,7 @@ function render(gobj) {
73168
73271
  return;
73169
73272
  }
73170
73273
  if (state === "ST_IDLE") {
73171
- show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected", priv.load_error);
73274
+ show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected", priv.load_error, true);
73172
73275
  destroy_diagram(gobj);
73173
73276
  clear($body);
73174
73277
  return;
@@ -75169,13 +75272,18 @@ function run_next_write(gobj) {
75169
75272
  let write = priv.save_queue[0];
75170
75273
  let command = write_command(write.op);
75171
75274
  let options = write_options(write.op);
75275
+ if (!transport_in_session(gobj)) {
75276
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no session, '${command}' of '${write.topic_name}' was not sent`);
75277
+ priv.reload_on_open = true;
75278
+ return end_writes(gobj, "cannot reach the treedb");
75279
+ }
75172
75280
  priv.write_tag++;
75173
75281
  if (remote_command(gobj, command, {
75174
75282
  topic_name: write.topic_name,
75175
75283
  record: write.record,
75176
75284
  options,
75177
75285
  record_id: write.record.id || ""
75178
- }, { write: priv.write_tag }) < 0) return end_writes(gobj, (0, i18next.t)("cannot reach the treedb"));
75286
+ }, { write: priv.write_tag }) < 0) return end_writes(gobj, "cannot reach the treedb");
75179
75287
  return 0;
75180
75288
  }
75181
75289
  /***************************************************************