@yuneta/gobj-ui 7.25.14 → 7.25.16

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.
Files changed (35) hide show
  1. package/README.md +100 -4
  2. package/dist/gobj-ui.cjs.js +406 -94
  3. package/dist/gobj-ui.es.js +406 -94
  4. package/package.json +2 -2
  5. package/src/c_g6_nodes_tree.js +79 -15
  6. package/src/c_yui_schema_editor.js +343 -66
  7. package/src/c_yui_treedb_graph.js +53 -6
  8. package/src/c_yui_treedb_topic_with_form.js +10 -10
  9. package/src/c_yui_treedb_topics.js +7 -7
  10. package/src/form_busy.js +2 -3
  11. package/src/form_busy.test.js +1 -1
  12. package/src/form_time_value.js +1 -1
  13. package/src/form_time_value.test.js +1 -1
  14. package/src/form_writes_in_flight.js +1 -2
  15. package/src/form_writes_in_flight.test.js +1 -1
  16. package/src/g6_graphs_write_owed.wiring.test.js +200 -0
  17. package/src/g6_nodes_tree_fsm.test.js +33 -0
  18. package/src/graph_save_plan.js +28 -0
  19. package/src/graph_save_plan.test.js +36 -2
  20. package/src/host_drafts.js +2 -3
  21. package/src/host_drafts.test.js +2 -2
  22. package/src/schema_editor_drafts.wiring.test.js +2 -2
  23. package/src/schema_editor_reload.wiring.test.js +329 -49
  24. package/src/schema_editor_transport.wiring.test.js +2 -2
  25. package/src/schema_version_bump.test.js +1 -1
  26. package/src/shell_modals.js +31 -11
  27. package/src/shell_toast_once.test.js +73 -10
  28. package/src/table_cell_text.js +1 -1
  29. package/src/table_cell_text.test.js +1 -1
  30. package/src/treedb_graph_writes.wiring.test.js +208 -0
  31. package/src/treedb_topics_transport.wiring.test.js +2 -2
  32. package/src/yui_install.js +14 -3
  33. package/src/yui_install.test.js +23 -0
  34. package/src/yui_row_search.test.js +1 -1
  35. package/src/yui_table_select.test.js +1 -1
@@ -3033,9 +3033,8 @@ function maybe_apply_translator($node, opts) {
3033
3033
  * it -- until every handle is closed or has timed out. Handed the
3034
3034
  * first caller's handle and timer, a repeat asking `timeout: 0`
3035
3035
  * was dismissed on the first one's timer, and closing one
3036
- * caller's handle closed a toast another was still showing
3037
- * (third independent review). The operator's ✕ closes it for
3038
- * everybody.
3036
+ * caller's handle closed a toast another was still showing.
3037
+ * The operator's ✕ closes it for everybody.
3039
3038
  ***************************************************************/
3040
3039
  function show_notification(shell, kind, message, opts) {
3041
3040
  let $layer = notification_layer(shell);
@@ -3433,8 +3432,28 @@ function build_dialog(shell, message, buttons, opts) {
3433
3432
  if ($primary) $primary.focus();
3434
3433
  });
3435
3434
  }
3435
+ /***************************************************************
3436
+ * The labels a confirmation shows when the caller names none.
3437
+ *
3438
+ * They are i18n KEYS, lower-case like every key: a button
3439
+ * translates its label with the app's t() -- its text, its title
3440
+ * and its aria-label. Until 7.25.16 they were "OK", "Yes", "No",
3441
+ * "Delete" and "Cancel", keys that no locale carries, so a dialog
3442
+ * that took the defaults read in English in every language.
3443
+ *
3444
+ * Written as `i18n:` entries so the validate-locales script of a
3445
+ * consumer finds them when it scans this module: an app that
3446
+ * mounts these dialogs must define the five keys.
3447
+ ***************************************************************/
3448
+ var DEFAULT_LABEL = Object.freeze({
3449
+ ok: Object.freeze({ i18n: "ok" }),
3450
+ yes: Object.freeze({ i18n: "yes" }),
3451
+ no: Object.freeze({ i18n: "no" }),
3452
+ delete: Object.freeze({ i18n: "delete" }),
3453
+ cancel: Object.freeze({ i18n: "cancel" })
3454
+ });
3436
3455
  function yui_shell_confirm_ok(shell, message, opts) {
3437
- let label = opts && opts.ok_label || "OK";
3456
+ let label = opts && opts.ok_label || DEFAULT_LABEL.ok.i18n;
3438
3457
  if (!opts || !opts.type) opts = Object.assign({}, opts, { type: "success" });
3439
3458
  return build_dialog(shell, message, [{
3440
3459
  label,
@@ -3443,8 +3462,8 @@ function yui_shell_confirm_ok(shell, message, opts) {
3443
3462
  }], opts).then(() => void 0);
3444
3463
  }
3445
3464
  function yui_shell_confirm_yesno(shell, message, opts) {
3446
- let yes_label = opts && opts.yes_label || "Yes";
3447
- let no_label = opts && opts.no_label || "No";
3465
+ let yes_label = opts && opts.yes_label || DEFAULT_LABEL.yes.i18n;
3466
+ let no_label = opts && opts.no_label || DEFAULT_LABEL.no.i18n;
3448
3467
  return build_dialog(shell, message, [{
3449
3468
  label: yes_label,
3450
3469
  value: "yes",
@@ -3469,8 +3488,8 @@ function yui_shell_confirm_yesno(shell, message, opts) {
3469
3488
  * pressed.
3470
3489
  ***************************************************************/
3471
3490
  function yui_shell_confirm_danger(shell, message, opts) {
3472
- let confirm_label = opts && opts.confirm_label || "Delete";
3473
- let cancel_label = opts && opts.cancel_label || "Cancel";
3491
+ let confirm_label = opts && opts.confirm_label || DEFAULT_LABEL.delete.i18n;
3492
+ let cancel_label = opts && opts.cancel_label || DEFAULT_LABEL.cancel.i18n;
3474
3493
  if (!opts || !opts.type) opts = Object.assign({}, opts, { type: "danger" });
3475
3494
  return build_dialog(shell, message, [{
3476
3495
  label: confirm_label,
@@ -3482,9 +3501,9 @@ function yui_shell_confirm_danger(shell, message, opts) {
3482
3501
  }], opts).then((v) => v === "confirm");
3483
3502
  }
3484
3503
  function yui_shell_confirm_yesnocancel(shell, message, opts) {
3485
- let yes_label = opts && opts.yes_label || "Yes";
3486
- let no_label = opts && opts.no_label || "No";
3487
- let cancel_label = opts && opts.cancel_label || "Cancel";
3504
+ let yes_label = opts && opts.yes_label || DEFAULT_LABEL.yes.i18n;
3505
+ let no_label = opts && opts.no_label || DEFAULT_LABEL.no.i18n;
3506
+ let cancel_label = opts && opts.cancel_label || DEFAULT_LABEL.cancel.i18n;
3488
3507
  return build_dialog(shell, message, [
3489
3508
  {
3490
3509
  label: yes_label,
@@ -7420,7 +7439,7 @@ function json_diff_rows(doc_a, doc_b) {
7420
7439
  * A Date -> the value of a `datetime-local` input, in local time,
7421
7440
  * WITH seconds: the input is built with `step: 1`, which is what
7422
7441
  * lets it hold them. Written as "YYYY-MM-DDTHH:mm" it lost them on
7423
- * every save of any field (M26 of the 2026-09-21 review).
7442
+ * every save of any field.
7424
7443
  ************************************************************/
7425
7444
  function date_to_datetime_local(value) {
7426
7445
  if (!(value instanceof Date) || isNaN(value.getTime())) return "";
@@ -23125,8 +23144,7 @@ function yui_shell_show_route_map(shell, opts) {
23125
23144
  * answers, so the host has to answer EVERY way a write ends --
23126
23145
  * and the transport closing under a write in flight, or a
23127
23146
  * command that never left because there was no session, are two
23128
- * of them that nobody answered: the form stayed busy for ever
23129
- * (N8 of the 2026-09-22 review).
23147
+ * of them that nobody answered: the form stayed busy for ever.
23130
23148
  *
23131
23149
  * A write is known by its TOPIC and its `form_write` serial,
23132
23150
  * never by the serial alone: each topic has its own form, and
@@ -25677,8 +25695,8 @@ function ac_update_record(gobj, event, kw, src) {
25677
25695
  /************************************************************
25678
25696
  * A write sent with no session never leaves: C_IEVENT_CLI logs
25679
25697
  * "Not in session" and answers null, as it does when the command
25680
- * went -- so a form waiting for the answer waited for ever (N8 of
25681
- * the 2026-09-22 review). Refused before it is sent, aloud.
25698
+ * went -- so a form waiting for the answer waited for ever. Refused
25699
+ * before it is sent, aloud.
25682
25700
  ************************************************************/
25683
25701
  function refuse_without_session(gobj, topic_name) {
25684
25702
  if (is_connected$1(gobj)) return false;
@@ -26790,9 +26808,8 @@ function delete_impact(desc, records) {
26790
26808
  * that back. So a second busy(true) while already busy must not
26791
26809
  * run at all -- it read the buttons it had just disabled as
26792
26810
  * "disabled on their own", and the busy(false) of a refused write
26793
- * left Save and Cancel dead (N9 of the 2026-09-22 review: a record
26794
- * with a picked file went busy for the read, then busy again for
26795
- * the write). The caller owns `state` ({busy}) for the form's life.
26811
+ * left Save and Cancel dead (a record with a picked file went
26812
+ * busy for the read, then busy again for the write). The caller owns `state` ({busy}) for the form's life.
26796
26813
  *
26797
26814
  * Copyright (c) 2026, ArtGins.
26798
26815
  * All Rights Reserved.
@@ -26830,7 +26847,7 @@ function set_toolbar_busy($container, state, busy) {
26830
26847
  * (only its own `plaintext` formatter escapes), so a record's text
26831
26848
  * returned as a string is parsed as markup: `a<b and c>d` showed
26832
26849
  * as `ad`, and a field holding `<img src=x onerror=...>` ran it in
26833
- * the operator's browser (M31 of the 2026-09-21 review). Record
26850
+ * the operator's browser. Record
26834
26851
  * data goes in as a TEXT NODE, and a cell built around it as DOM,
26835
26852
  * never as an HTML string with the data interpolated.
26836
26853
  *
@@ -29592,7 +29609,7 @@ function ac_new_row(gobj, event, kw, src) {
29592
29609
  * What crosses the confirmation is the rows' IDENTITY, never their
29593
29610
  * position: the view applies EV_TREEDB_NODE_* of every writer while the
29594
29611
  * dialog is open, and a position names whatever row sits there when the
29595
- * person answers -- with force:true (A6 of the 2026-09-21 review).
29612
+ * person answers -- with force:true.
29596
29613
  ************************************************************/
29597
29614
  function ac_delete_rows(gobj, event, kw, src) {
29598
29615
  if (refuse_if_readonly$2(gobj, event)) return -1;
@@ -29803,8 +29820,8 @@ function ac_files_read(gobj, event, kw, src) {
29803
29820
  * open and BUSY, and the serial it waits for is taken here.
29804
29821
  *
29805
29822
  * A refused write used to close the form all the same and throw
29806
- * away what was typed, although the README said it stayed open
29807
- * (M25 of the 2026-09-21 review): the close was posted with the
29823
+ * away what was typed, although the README said it stayed open:
29824
+ * the close was posted with the
29808
29825
  * publish, before the backend had answered anything.
29809
29826
  ************************************************************/
29810
29827
  function wait_for_write_answer(gobj) {
@@ -30976,6 +30993,28 @@ function apply_graphs_echo(live, saved, records, rec) {
30976
30993
  delete saved[rec.topic];
30977
30994
  }
30978
30995
  }
30996
+ /***************************************************************
30997
+ * A write of one topic's arrangement did NOT land: the backend
30998
+ * refused it, the transport refused it on the way out, or there
30999
+ * was no session to send it on.
31000
+ *
31001
+ * saved {topic_name: properties} what the backend has
31002
+ * topic_name the topic of the refused record
31003
+ *
31004
+ * The view records a write as saved BEFORE it leaves, because
31005
+ * nothing answers it when it lands. So a refused one left the view
31006
+ * believing the backend held an arrangement it does not, and the
31007
+ * next Save found nothing to write. What the backend holds for the
31008
+ * topic is not known any more, so it is forgotten: the next Save
31009
+ * writes the topic again (plan_graph_saves()).
31010
+ *
31011
+ * Returns TRUE if there was something to forget.
31012
+ ***************************************************************/
31013
+ function forget_refused_graphs_write(saved, topic_name) {
31014
+ if (!is_object(saved) || !topic_name || !Object.prototype.hasOwnProperty.call(saved, topic_name)) return false;
31015
+ delete saved[topic_name];
31016
+ return true;
31017
+ }
30979
31018
  //#endregion
30980
31019
  //#region src/treedb_node_label.js
30981
31020
  /***********************************************************************
@@ -59208,6 +59247,7 @@ var PRIVATE_DATA$4 = {
59208
59247
  __graphs__: [],
59209
59248
  _graph_properties: {},
59210
59249
  _saved_graph_properties: {},
59250
+ _graphs_writes_owed: {},
59211
59251
  yet_showed: false,
59212
59252
  edit_mode: false,
59213
59253
  operation_mode: null,
@@ -60903,6 +60943,7 @@ function save_topic_graph_properties(gobj, topic_name) {
60903
60943
  if (!topic_arrangement_changed(properties, priv._saved_graph_properties[topic_name])) return;
60904
60944
  properties.__origin__ = gobj_read_str_attr(gobj_yuno(), "node_uuid");
60905
60945
  priv._saved_graph_properties[topic_name] = json_deep_copy(properties);
60946
+ delete priv._graphs_writes_owed[topic_name];
60906
60947
  let kw_update = {
60907
60948
  treedb_name: priv.treedb_name,
60908
60949
  topic_name: "__graphs__",
@@ -60984,8 +61025,13 @@ function update_history_buttons(gobj) {
60984
61025
  } else {
60985
61026
  disableElements($container, ".EV_HISTORY_UNDO");
60986
61027
  set_active_state($container, ".EV_HISTORY_UNDO", false);
60987
- disableElements($container, ".EV_SAVE_GRAPH");
60988
- set_submit_state($container, ".EV_SAVE_GRAPH", false);
61028
+ if (graphs_write_is_owed(gobj)) {
61029
+ enableElements($container, ".EV_SAVE_GRAPH");
61030
+ set_submit_state($container, ".EV_SAVE_GRAPH", true);
61031
+ } else {
61032
+ disableElements($container, ".EV_SAVE_GRAPH");
61033
+ set_submit_state($container, ".EV_SAVE_GRAPH", false);
61034
+ }
60989
61035
  }
60990
61036
  } else {
60991
61037
  disableElements($container, ".EV_HISTORY_REDO");
@@ -61942,6 +61988,15 @@ function run_preview_undo(gobj, key) {
61942
61988
  log_error(`${gobj_short_name(gobj)}: cannot undo the preview: ${e}`);
61943
61989
  }
61944
61990
  }
61991
+ /************************************************************
61992
+ * Is a `__graphs__` write owed? The host said one did not land
61993
+ * (EV_GRAPHS_WRITE_REFUSED), and no Save has written that topic
61994
+ * since. The Save button stays lit while it is: the history
61995
+ * knows nothing of it, so `canUndo()` alone would put it out.
61996
+ ************************************************************/
61997
+ function graphs_write_is_owed(gobj) {
61998
+ return Object.keys(gobj.priv._graphs_writes_owed).length > 0;
61999
+ }
61945
62000
  function mark_graph_dirty(gobj) {
61946
62001
  let $container = gobj_read_attr(gobj, "$container");
61947
62002
  enableElements($container, ".EV_SAVE_GRAPH");
@@ -65682,6 +65737,7 @@ function ac_clear_data(gobj, event, kw, src) {
65682
65737
  deselect_node(gobj);
65683
65738
  hide_create_popover(gobj);
65684
65739
  gobj_write_attr(gobj, "records", {});
65740
+ priv._graphs_writes_owed = {};
65685
65741
  priv._nodes_total = 0;
65686
65742
  priv._nodes_placed = 0;
65687
65743
  priv._fold_model = null;
@@ -65774,15 +65830,36 @@ function ac_save_graph(gobj, event, kw, src) {
65774
65830
  disableElements($container, ".EV_SAVE_GRAPH");
65775
65831
  set_submit_state($container, ".EV_SAVE_GRAPH", false);
65776
65832
  let history = graph_get_plugin(gobj, "history");
65777
- if (history) {
65778
- history.clear();
65779
- update_history_buttons(gobj);
65780
- }
65833
+ if (history) history.clear();
65781
65834
  save_geometry(gobj);
65835
+ update_history_buttons(gobj);
65782
65836
  }
65783
65837
  return 0;
65784
65838
  }
65785
65839
  /************************************************************
65840
+ * The host says a write of `__graphs__` did not land: the
65841
+ * backend refused it, the transport refused it on the way out,
65842
+ * or there was no session to send it on. kw: {topic}
65843
+ *
65844
+ * save_topic_graph_properties() recorded it as saved before it
65845
+ * left. Forgotten now, and the Save is lit again: the next Save
65846
+ * writes the topic again. Before, the view believed the backend
65847
+ * held the arrangement, and the next Save had nothing to write.
65848
+ ************************************************************/
65849
+ function ac_graphs_write_refused(gobj, event, kw, src) {
65850
+ let priv = gobj.priv;
65851
+ let topic_name = kw && kw.topic;
65852
+ if (!topic_name) {
65853
+ log_error(`${gobj_short_name(gobj)}: ${event} names no topic`);
65854
+ return -1;
65855
+ }
65856
+ log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' was not saved: the next Save writes it again`);
65857
+ forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
65858
+ priv._graphs_writes_owed[topic_name] = true;
65859
+ if (priv.edit_mode) mark_graph_dirty(gobj);
65860
+ return 0;
65861
+ }
65862
+ /************************************************************
65786
65863
  * Node created, from subscription
65787
65864
  ************************************************************/
65788
65865
  function ac_node_created(gobj, event, kw, src) {
@@ -66850,6 +66927,11 @@ function create_gclass$4(gclass_name) {
66850
66927
  ac_node_deleted,
66851
66928
  null
66852
66929
  ],
66930
+ [
66931
+ "EV_GRAPHS_WRITE_REFUSED",
66932
+ ac_graphs_write_refused,
66933
+ null
66934
+ ],
66853
66935
  [
66854
66936
  "EV_NODE_CLICK",
66855
66937
  ac_node_click$2,
@@ -67103,6 +67185,7 @@ function create_gclass$4(gclass_name) {
67103
67185
  ["EV_NODE_CREATED", 0],
67104
67186
  ["EV_NODE_UPDATED", 0],
67105
67187
  ["EV_NODE_DELETED", 0],
67188
+ ["EV_GRAPHS_WRITE_REFUSED", 0],
67106
67189
  ["EV_NODE_CLICK", 0],
67107
67190
  ["EV_EDGE_CLICK", 0],
67108
67191
  ["EV_NODE_CONTEXT_MENU", 0],
@@ -68279,7 +68362,7 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68279
68362
  let priv = gobj.priv;
68280
68363
  if (!priv.gobj_remote_yuno) {
68281
68364
  log_error(`${gobj_short_name(gobj)}: No gobj_remote_yuno defined`);
68282
- return;
68365
+ return -1;
68283
68366
  }
68284
68367
  let command = "update-node";
68285
68368
  let kw = {
@@ -68291,7 +68374,11 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68291
68374
  };
68292
68375
  kw.__md_command__ = { topic_name };
68293
68376
  let ret = gobj_command(priv.gobj_remote_yuno, command, kw, gobj);
68294
- if (ret) log_error(ret);
68377
+ if (ret) {
68378
+ log_error(ret);
68379
+ return -1;
68380
+ }
68381
+ return 0;
68295
68382
  }
68296
68383
  /************************************************************
68297
68384
  * Command to remote service
@@ -68564,6 +68651,7 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68564
68651
  case "delete-node":
68565
68652
  case "link-nodes":
68566
68653
  case "unlink-nodes":
68654
+ if (result < 0 && command === "update-node") graphs_write_refused(gobj, kw_get_str(gobj, kw_command, "topic_name", "", 0), kw_get_dict(gobj, kw_command, "record", {}, 0));
68567
68655
  if (result >= 0 && kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
68568
68656
  treedb_name: kw_get_str(gobj, kw_command, "treedb_name", "", 0),
68569
68657
  topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
@@ -68807,12 +68895,40 @@ function ac_create_node(gobj, event, kw, src) {
68807
68895
  * Send to backend
68808
68896
  ********************************************/
68809
68897
  function ac_update_node(gobj, event, kw, src) {
68810
- let priv = gobj.priv;
68811
- if (refuse_if_readonly$1(gobj, event)) return -1;
68812
- let treedb_name = priv.treedb_name;
68898
+ let treedb_name = gobj.priv.treedb_name;
68813
68899
  let topic_name = kw.topic_name;
68814
68900
  let record = kw.record;
68815
- return treedb_update_node(gobj, treedb_name, topic_name, record, kw.options || {});
68901
+ let options = kw.options || {};
68902
+ if (refuse_if_readonly$1(gobj, event)) {
68903
+ graphs_write_refused(gobj, topic_name, record);
68904
+ return -1;
68905
+ }
68906
+ if (topic_name === "__graphs__" && !is_connected(gobj)) {
68907
+ log_warning(`${gobj_short_name(gobj)}: no session, the arrangement was not sent`);
68908
+ graphs_write_refused(gobj, topic_name, record);
68909
+ return -1;
68910
+ }
68911
+ let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
68912
+ if (ret < 0) graphs_write_refused(gobj, topic_name, record);
68913
+ return ret;
68914
+ }
68915
+ /************************************************************
68916
+ * A write of `__graphs__` did not land. The engine
68917
+ * (C_G6_NODES_TREE) took it for granted when it left -- nothing
68918
+ * answers it when it lands -- so it is told, and its next Save
68919
+ * writes the topic again. Other topics are the operator's data:
68920
+ * the answer's error says what happened to them.
68921
+ ************************************************************/
68922
+ function graphs_write_refused(gobj, topic_name, record) {
68923
+ let priv = gobj.priv;
68924
+ if (topic_name !== "__graphs__") return;
68925
+ let topic = record && typeof record === "object" ? record.topic || record.id : "";
68926
+ if (!topic) {
68927
+ log_error(`${gobj_short_name(gobj)}: a refused __graphs__ write names no topic`);
68928
+ return;
68929
+ }
68930
+ if (!priv.gobj_nodes_tree) return;
68931
+ gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", { topic }, gobj);
68816
68932
  }
68817
68933
  /********************************************
68818
68934
  * Message from G6_nodes_tree
@@ -70468,9 +70584,8 @@ function register_c_yui_treedb_schema() {
70468
70584
  * it, and that mark lived in the memory of the session only:
70469
70585
  * a reload of the page, a reconnect or a refresh of the model
70470
70586
  * lost the chip, the banner and the export warning while
70471
- * __system__ still differed from the file in use (N13 of the
70472
- * 2026-09-22 review). The host reads that difference from
70473
- * C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
70587
+ * __system__ still differed from the file in use. The host
70588
+ * reads that difference from C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
70474
70589
  * hands it here; the editor applies it to its model, in place
70475
70590
  * of what the host said before.
70476
70591
  *
@@ -72138,11 +72253,10 @@ function write_options(op) {
72138
72253
  * AN EDIT IS A DRAFT. A write here moves no version and reaches no
72139
72254
  * treedb: the host SAVES it (C_TREEDB's save-schema raises the
72140
72255
  * topic_version of what changed and the schema_version, once) and
72141
- * APPLIES it (apply-schema + a restart of the owning yuno). Until
72142
- * the owner's design of M36 (2026-09-21 review) every write here
72143
- * raised both versions, so an edit half made was already the
72144
- * schema of the next start. The topic list marks what this session
72145
- * wrote and has not saved (EV_REFRESH forgets it: the view's own
72256
+ * APPLIES it (apply-schema + a restart of the owning yuno). If a
72257
+ * write here raised both versions, an edit half made would already
72258
+ * be the schema of the next start. The topic list marks what this
72259
+ * session wrote and has not saved (EV_REFRESH forgets it: the view's own
72146
72260
  * Refresh button sends it, and a host may after a save -- the
72147
72261
  * agent console's does not) and what the host says is not saved
72148
72262
  * (EV_DRAFTS, replaced whole each time it is sent).
@@ -72208,6 +72322,7 @@ var HOOK_TOPICS = "topics";
72208
72322
  var HOOK_COLS = "cols";
72209
72323
  var DIAGRAM_SEG = "diagram";
72210
72324
  var PURPOSE = "schema_editor";
72325
+ var DIALOGS_THAT_READ_NOTHING = ["SCHEMA_EXPORT_DIALOG", "SCHEMA_REPORT_DIALOG"];
72211
72326
  /***************************************************************
72212
72327
  * Attrs
72213
72328
  ***************************************************************/
@@ -72240,6 +72355,7 @@ var PRIVATE_DATA$1 = {
72240
72355
  diagram_gobj: null,
72241
72356
  dialog: null,
72242
72357
  dialog_gen: 0,
72358
+ dialog_kind: "",
72243
72359
  save_queue: null,
72244
72360
  save_return: "",
72245
72361
  save_wrote: false,
@@ -72249,6 +72365,8 @@ var PRIVATE_DATA$1 = {
72249
72365
  dropped_round: 0,
72250
72366
  dropped_write: 0,
72251
72367
  reload_after_write: false,
72368
+ refresh_after_write: false,
72369
+ reopen_form: null,
72252
72370
  pending_seg: null,
72253
72371
  import_plan: null,
72254
72372
  import_pane: null,
@@ -72408,9 +72526,9 @@ function refuse_if_readonly(gobj, event) {
72408
72526
  * dumped by the machine trace).
72409
72527
  *
72410
72528
  * Refused, and said. A column form sends every field it shows, so
72411
- * its Save after a reload wrote the OLD record over the newer one
72412
- * (fourth independent review, probe A3); a form whose column the
72413
- * reload removed answered "names no column" for ever. end_load()
72529
+ * its Save after a reload wrote the OLD record over the newer one;
72530
+ * a form whose column the reload removed answered "names no column"
72531
+ * for ever. end_load()
72414
72532
  * closes such a dialog when the load lands; this is the same rule
72415
72533
  * for whatever arrives anyway -- a stale screen, a confirmation
72416
72534
  * that was up, a keyboard.
@@ -72430,8 +72548,7 @@ function built_on_a_replaced_model(gobj, event, kw) {
72430
72548
  * the model shown is kept, and it may be older than the store.
72431
72549
  * The session is up, so no reconnect will ask for it again -- it
72432
72550
  * was owed for as long as the session lasted, and a form opened
72433
- * on that model wrote every field of it over the newer record
72434
- * (fifth independent review; A3 of the fourth, by another path).
72551
+ * on that model wrote every field of it over the newer record.
72435
72552
  *
72436
72553
  * So the operator's NEXT action asks it. An edit -- a form, a
72437
72554
  * write, a confirmation, what reads the model whole (check,
@@ -72455,14 +72572,61 @@ function owes_reload(gobj) {
72455
72572
  let state = gobj_current_state(gobj);
72456
72573
  return gobj.priv.reload_on_open && transport_in_session(gobj) && state !== "ST_LOADING" && state !== "ST_SAVING";
72457
72574
  }
72458
- function reload_first(gobj, event) {
72575
+ function reload_first(gobj, event, reopen) {
72576
+ let priv = gobj.priv;
72459
72577
  if (!owes_reload(gobj)) return false;
72460
- gobj.priv.reload_on_open = false;
72578
+ priv.reload_on_open = false;
72579
+ if (reopen && priv.dialog) {
72580
+ priv.reopen_form = Object.assign(json_deep_copy(reopen), {
72581
+ dialog: priv.dialog,
72582
+ treedb_id: priv.treedb_id
72583
+ });
72584
+ log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas shown may be out of date: they are read again first, and the form opens again on them with its changes`);
72585
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas shown may be out of date: they are read again, and the form opens again on them with your changes", { t });
72586
+ request_model(gobj, true);
72587
+ return true;
72588
+ }
72461
72589
  log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas shown may be out of date: they are read again first`);
72462
72590
  yui_shell_show_error(yui_shell_of(gobj), "the schemas shown may be out of date: they are read again, try again when they are in", { t });
72463
72591
  request_model(gobj, true);
72464
72592
  return true;
72465
72593
  }
72594
+ /***************************************************************
72595
+ * The form reload_first() took down, opened again on the model
72596
+ * the load just made, with the operator's changes on top.
72597
+ *
72598
+ * Only where it can mean the same thing: on the treedb it was
72599
+ * opened on, on the screen whose Save it has, and on a record
72600
+ * that is still there. Otherwise it is not opened, and that is
72601
+ * said -- a form opened on another treedb, or on nothing, would
72602
+ * save somewhere the operator never meant.
72603
+ ***************************************************************/
72604
+ function reopen_form(gobj, r) {
72605
+ let priv = gobj.priv;
72606
+ let state = gobj_current_state(gobj);
72607
+ let treedb = current_treedb(gobj);
72608
+ let topic = null;
72609
+ let col = null;
72610
+ let fits = false;
72611
+ if (treedb && r.treedb_id === priv.treedb_id) {
72612
+ if (r.form === "column" && state === "ST_COLUMNS") {
72613
+ topic = find_topic(priv.model, priv.treedb_id, r.topic);
72614
+ col = topic && !r.creating ? find_col(priv.model, priv.treedb_id, r.topic, r.col) : null;
72615
+ fits = !!topic && (r.creating || !!col);
72616
+ } else if (r.form === "topic" && state === "ST_TOPICS") {
72617
+ topic = r.creating ? null : find_topic(priv.model, priv.treedb_id, r.topic);
72618
+ fits = r.creating || !!topic;
72619
+ }
72620
+ }
72621
+ if (!fits) {
72622
+ log_warning(`${gobj_short_name(gobj)}: the ${r.form} form was not opened again: what it edited is not on the schemas the reload read (${r.treedb_id} ${r.topic}${r.col ? "." + r.col : ""}, now ${state})`);
72623
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again and what the form was editing is not there any more", { t });
72624
+ return -1;
72625
+ }
72626
+ if (r.form === "column") open_column_form(gobj, topic, col, null, r.changed);
72627
+ else open_topic_form(gobj, treedb, topic, r.changed);
72628
+ return 0;
72629
+ }
72466
72630
  function reload_after_move(gobj) {
72467
72631
  if (!owes_reload(gobj)) return 0;
72468
72632
  gobj.priv.reload_on_open = false;
@@ -72481,7 +72645,7 @@ function reload_after_move(gobj) {
72481
72645
  * none of the actions of a screen, so the screen it replaces goes
72482
72646
  * and the toolbar stops answering: left up, a click on a card, on
72483
72647
  * Back or on the drawing answered "Event NOT DEFINED in state
72484
- * ST_LOADING" and was lost (third independent review). A dialog
72648
+ * ST_LOADING" and was lost. A dialog
72485
72649
  * left open is answered in ST_LOADING instead (ac_wait_for_the_load),
72486
72650
  * and a load that LANDS closes it (end_load()): it was built on the
72487
72651
  * model the load replaced, and its Save sends every field it shows.
@@ -72608,7 +72772,7 @@ function build_model(gobj) {
72608
72772
  * measurement of "changed since" can be measured against.
72609
72773
  *
72610
72774
  * `written` is forgotten only by an EV_REFRESH (the view's own
72611
- * Refresh button, or a host that sends one). Since M36 a write moves no
72775
+ * Refresh button, or a host that sends one). Since 7.23.196 a write moves no
72612
72776
  * version, so `written` is the draft chip of what this session
72613
72777
  * wrote and no longer half of a version measurement: a reload of
72614
72778
  * the reconnect, or of a write that turned out done, forgot the
@@ -73701,6 +73865,7 @@ function close_dialog(gobj) {
73701
73865
  if (!priv.dialog) return;
73702
73866
  let dialog = priv.dialog;
73703
73867
  priv.dialog = null;
73868
+ priv.dialog_kind = "";
73704
73869
  priv.export_view = null;
73705
73870
  try {
73706
73871
  dialog.close();
@@ -73712,6 +73877,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
73712
73877
  let priv = gobj.priv;
73713
73878
  close_dialog(gobj);
73714
73879
  priv.dialog_gen = priv.model_gen;
73880
+ priv.dialog_kind = logical;
73715
73881
  priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
73716
73882
  dialog: true,
73717
73883
  title,
@@ -73720,6 +73886,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
73720
73886
  t,
73721
73887
  on_close: () => {
73722
73888
  priv.dialog = null;
73889
+ priv.dialog_kind = "";
73723
73890
  priv.export_view = null;
73724
73891
  }
73725
73892
  });
@@ -73883,6 +74050,55 @@ function read_form($root) {
73883
74050
  return out;
73884
74051
  }
73885
74052
  /***************************************************************
74053
+ * A column form as its Save sends it: the fields and the flags.
74054
+ ***************************************************************/
74055
+ function column_form_state($form) {
74056
+ let values = read_form($form);
74057
+ values.flag = read_flags($form);
74058
+ return values;
74059
+ }
74060
+ /***************************************************************
74061
+ * The fields the operator CHANGED: those whose value is not the
74062
+ * one the form was opened with.
74063
+ ***************************************************************/
74064
+ function form_changes(initial, values) {
74065
+ let changed = {};
74066
+ for (let name of Object.keys(values)) if (JSON.stringify(values[name]) !== JSON.stringify(initial[name])) changed[name] = values[name];
74067
+ return changed;
74068
+ }
74069
+ /***************************************************************
74070
+ * Put changes back into a form just built (reopen_form()). The
74071
+ * flags are drawn again for the type the changes leave, holding
74072
+ * the flags they name, so none of them is hidden by the type.
74073
+ ***************************************************************/
74074
+ function apply_form_changes(gobj, $form, changes) {
74075
+ for (let name of Object.keys(changes)) {
74076
+ if (name === "flag") continue;
74077
+ let $control = $form.querySelector(`[data-name="${name}"]`);
74078
+ if (!$control) {
74079
+ log_warning(`${gobj_short_name(gobj)}: a change of '${name}' has no field in the form opened again: not put back`);
74080
+ continue;
74081
+ }
74082
+ if ($control.type === "checkbox") $control.checked = !!changes[name];
74083
+ else $control.value = changes[name];
74084
+ }
74085
+ let $type = $form.querySelector("[data-name=\"type\"]");
74086
+ if ($type && $form.querySelector(".SCHEMA_FLAGS")) {
74087
+ let flags = Array.isArray(changes.flag) ? changes.flag : read_flags($form);
74088
+ redraw_flags(gobj, $form, $type.value, flags);
74089
+ }
74090
+ }
74091
+ /***************************************************************
74092
+ * Draw the flags of a column form again, for `type`, with
74093
+ * `current` checked.
74094
+ ***************************************************************/
74095
+ function redraw_flags(gobj, $form, type, current) {
74096
+ let $holder = $form.querySelector(".SCHEMA_FLAGS");
74097
+ let $new = createElement2(flags_field(gobj, type, current));
74098
+ $holder.parentNode.replaceChild($new, $holder);
74099
+ refresh_language($new, t);
74100
+ }
74101
+ /***************************************************************
73886
74102
  * The flags of a column, as checkboxes that say what they do.
73887
74103
  *
73888
74104
  * Grouped the way they act, greyed when they are meaningless on
@@ -73956,7 +74172,7 @@ function read_flags($root) {
73956
74172
  * Offering a field that silently does something else is worse
73957
74173
  * than not offering it.
73958
74174
  ***************************************************************/
73959
- function open_column_form(gobj, topic, col, prefill) {
74175
+ function open_column_form(gobj, topic, col, prefill, changes) {
73960
74176
  let priv = gobj.priv;
73961
74177
  let record = prefill || col && col.record || {};
73962
74178
  let creating = !col;
@@ -74060,11 +74276,7 @@ function open_column_form(gobj, topic, col, prefill) {
74060
74276
  ]);
74061
74277
  let $type = $form.querySelector("[data-name=\"type\"]");
74062
74278
  $type.addEventListener("change", () => {
74063
- let $holder = $form.querySelector(".SCHEMA_FLAGS");
74064
- let current = read_flags($form);
74065
- let $new = createElement2(flags_field(gobj, $type.value, current));
74066
- $holder.parentNode.replaceChild($new, $holder);
74067
- refresh_language($new, t);
74279
+ redraw_flags(gobj, $form, $type.value, read_flags($form));
74068
74280
  });
74069
74281
  $form.addEventListener("change", (evt) => {
74070
74282
  let $box = evt.target.closest(".SCHEMA_FLAG_BOX");
@@ -74076,15 +74288,17 @@ function open_column_form(gobj, topic, col, prefill) {
74076
74288
  evt.stopPropagation();
74077
74289
  close_dialog(gobj);
74078
74290
  });
74291
+ let initial = column_form_state($form);
74292
+ if (changes) apply_form_changes(gobj, $form, changes);
74079
74293
  $form.querySelector(".SCHEMA_COL_FORM_SAVE").addEventListener("click", (evt) => {
74080
74294
  evt.stopPropagation();
74081
- let values = read_form($form);
74082
- values.flag = read_flags($form);
74295
+ let values = column_form_state($form);
74083
74296
  gobj_send_event(gobj, "EV_SAVE_COLUMN", {
74084
74297
  topic: topic.name,
74085
74298
  col: creating ? "" : col.name,
74086
74299
  creating,
74087
74300
  values,
74301
+ changed: creating ? values : form_changes(initial, values),
74088
74302
  model_gen
74089
74303
  }, gobj);
74090
74304
  });
@@ -74104,7 +74318,7 @@ function stringify_field(value) {
74104
74318
  * actually has: a pkey naming no column is a topic that does
74105
74319
  * not open, and it is the first thing the validator looks for.
74106
74320
  ***************************************************************/
74107
- function open_topic_form(gobj, treedb, topic) {
74321
+ function open_topic_form(gobj, treedb, topic, changes) {
74108
74322
  let record = topic && topic.record || {};
74109
74323
  let creating = !topic;
74110
74324
  let col_names = topic ? topic.cols.map((c) => c.name) : [];
@@ -74199,12 +74413,16 @@ function open_topic_form(gobj, treedb, topic) {
74199
74413
  evt.stopPropagation();
74200
74414
  close_dialog(gobj);
74201
74415
  });
74416
+ let initial = read_form($form);
74417
+ if (changes) apply_form_changes(gobj, $form, changes);
74202
74418
  $form.querySelector(".SCHEMA_TOPIC_FORM_SAVE").addEventListener("click", (evt) => {
74203
74419
  evt.stopPropagation();
74420
+ let values = read_form($form);
74204
74421
  gobj_send_event(gobj, "EV_SAVE_TOPIC", {
74205
74422
  topic: creating ? "" : topic.name,
74206
74423
  creating,
74207
- values: read_form($form),
74424
+ values,
74425
+ changed: creating ? values : form_changes(initial, values),
74208
74426
  model_gen
74209
74427
  }, gobj);
74210
74428
  });
@@ -74914,11 +75132,11 @@ function transport_in_session(gobj) {
74914
75132
  * The state is part of the question, and that is only true
74915
75133
  * because NOTHING moves the editor out of ST_LOADING or ST_SAVING
74916
75134
  * but the end of what is in flight: a position the host sends
74917
- * meanwhile waits in `pending_seg` (ac_show()). 7.25.6 let EV_SHOW
74918
- * move a RELOAD out of ST_LOADING, and every answer of it was then
74919
- * "for a load that is over": the records it had emptied stayed
74920
- * empty, and the next write patched them into a model with no
74921
- * treedb at all.
75135
+ * meanwhile waits in `pending_seg` (ac_show()). If EV_SHOW moved a
75136
+ * RELOAD out of ST_LOADING, every answer of it would be "for a load
75137
+ * that is over": the records it had emptied would stay empty, and
75138
+ * the next write would patch them into a model with no treedb at
75139
+ * all.
74922
75140
  ***************************************************************/
74923
75141
  function is_current_load(gobj, md) {
74924
75142
  return gobj_current_state(gobj) === "ST_LOADING" && kw_get_int(gobj, md, "round", 0, 0) === gobj.priv.load_round;
@@ -74974,20 +75192,28 @@ function transport_dropped(gobj) {
74974
75192
  function end_load(gobj) {
74975
75193
  let priv = gobj.priv;
74976
75194
  let seg = priv.pending_seg;
75195
+ let reopen = priv.reopen_form;
74977
75196
  priv.pending_seg = null;
75197
+ priv.reopen_form = null;
74978
75198
  if (priv.dialog && priv.dialog_gen !== priv.model_gen) {
74979
- log_warning(`${gobj_short_name(gobj)}: a dialog was open on the schemas the load replaced: closed`);
75199
+ if (!reopen || reopen.dialog !== priv.dialog) {
75200
+ reopen = null;
75201
+ log_warning(`${gobj_short_name(gobj)}: a dialog was open on the schemas the load replaced: closed`);
75202
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again: open the dialog again", { t });
75203
+ }
74980
75204
  close_dialog(gobj);
74981
- yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again: open the dialog again", { t });
74982
- }
75205
+ } else reopen = null;
75206
+ let ret = 0;
74983
75207
  if (priv.model) {
74984
- if (seg !== null) return apply_seg(gobj, seg);
74985
- return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
75208
+ if (seg !== null) ret = apply_seg(gobj, seg);
75209
+ else ret = go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
75210
+ } else {
75211
+ if (seg !== null) adopt_seg(gobj, seg);
75212
+ gobj_change_state(gobj, "ST_IDLE");
75213
+ render(gobj);
74986
75214
  }
74987
- if (seg !== null) adopt_seg(gobj, seg);
74988
- gobj_change_state(gobj, "ST_IDLE");
74989
- render(gobj);
74990
- return 0;
75215
+ if (reopen) reopen_form(gobj, reopen);
75216
+ return ret;
74991
75217
  }
74992
75218
  /***************************************************************
74993
75219
  * The queue is done, or gave up. Either way the model is what
@@ -75012,7 +75238,11 @@ function end_writes(gobj, error) {
75012
75238
  render(gobj);
75013
75239
  publish_check(gobj);
75014
75240
  }
75015
- reload_if_owed(gobj);
75241
+ if (priv.refresh_after_write) {
75242
+ priv.refresh_after_write = false;
75243
+ priv.reload_after_write = false;
75244
+ request_model(gobj, false);
75245
+ } else reload_if_owed(gobj);
75016
75246
  return error ? -1 : 0;
75017
75247
  }
75018
75248
  /***************************************************************
@@ -75124,10 +75354,59 @@ function position_seg(gobj) {
75124
75354
  * ...and the same in reverse. A tail naming a treedb or a topic
75125
75355
  * the store does not have lands one level up rather than on an
75126
75356
  * empty screen: a shared link outlives the schema it points at.
75357
+ *
75358
+ * Every position the HOST sends comes through here, at once
75359
+ * (ac_show()) or when what was in flight ends (end_load(),
75360
+ * end_writes()). A move to another position closes the dialog of
75361
+ * the screen the editor left (close_dialog_left_behind()).
75127
75362
  ***************************************************************/
75128
75363
  function apply_seg(gobj, seg) {
75364
+ let before = position_seg(gobj);
75129
75365
  let pos = seg_position(seg);
75130
- return go(gobj, pos.treedb_id, pos.topic_name, pos.diagram, false);
75366
+ let ret = go(gobj, pos.treedb_id, pos.topic_name, pos.diagram, false);
75367
+ if (position_seg(gobj) !== before) close_dialog_left_behind(gobj);
75368
+ return ret;
75369
+ }
75370
+ /***************************************************************
75371
+ * The host moved the editor while a dialog was up. C_YUI_SHELL
75372
+ * keeps the overlays open on a move that changes only the subpath
75373
+ * (a url typed in, a link), so the dialog outlives its screen.
75374
+ *
75375
+ * A form, the import and the orphans send events that only the
75376
+ * screen they were opened from declares, and they name their
75377
+ * topic under the treedb open at the time. Left up on another
75378
+ * screen, their Save answered "Event NOT DEFINED" and the edit was
75379
+ * lost with no word. So the dialog is closed and the operator told.
75380
+ * The import plan goes with its dialog: it was computed for the
75381
+ * treedb the editor left.
75382
+ *
75383
+ * The export and the check stay: they show the texts they were
75384
+ * opened with, and what they send is legal in every state.
75385
+ ***************************************************************/
75386
+ function close_dialog_left_behind(gobj) {
75387
+ let priv = gobj.priv;
75388
+ if (!priv.dialog || DIALOGS_THAT_READ_NOTHING.includes(priv.dialog_kind)) return;
75389
+ log_warning(`${gobj_short_name(gobj)}: the host moved the view while the dialog ${priv.dialog_kind} was open: closed`);
75390
+ close_dialog(gobj);
75391
+ forget_import_plan(gobj);
75392
+ yui_shell_show_error(yui_shell_of(gobj), "the view moved: open the dialog again", { t });
75393
+ }
75394
+ /***************************************************************
75395
+ * A confirmation answered on another screen than the one it was
75396
+ * asked from. A confirmation is a shell modal, so a move sent by
75397
+ * the host does not close it. It names its topic and column, and
75398
+ * the actions look them up under the treedb open NOW: a Yes after
75399
+ * a move to another treedb deleted the topic or column of the same
75400
+ * name THERE.
75401
+ *
75402
+ * Refused, and said. A kw with no `seg` stamp is not a
75403
+ * confirmation's and is not asked.
75404
+ ***************************************************************/
75405
+ function asked_on_another_screen(gobj, event, kw) {
75406
+ if (!kw || kw.seg === void 0 || kw.seg === position_seg(gobj)) return false;
75407
+ log_warning(`${gobj_short_name(gobj)}: ${event} refused, it was asked on '${kw.seg}' and the view is on '${position_seg(gobj)}'`);
75408
+ yui_shell_show_error(yui_shell_of(gobj), "the view moved: open the dialog again", { t });
75409
+ return true;
75131
75410
  }
75132
75411
  function seg_position(seg) {
75133
75412
  let parts = String(seg || "").split("/").filter((x) => x.length > 0);
@@ -75244,7 +75523,9 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75244
75523
  priv.save_return = "";
75245
75524
  priv.save_wrote = false;
75246
75525
  gobj_publish_event(gobj, "EV_RECORD_WRITTEN", { treedb_name: gobj_read_str_attr(gobj, "treedb_name") });
75247
- request_model(gobj, true);
75526
+ let refresh = priv.refresh_after_write;
75527
+ priv.refresh_after_write = false;
75528
+ request_model(gobj, !refresh);
75248
75529
  return 0;
75249
75530
  }
75250
75531
  }
@@ -75303,8 +75584,7 @@ function late_write_done(gobj, command, topic_name, data) {
75303
75584
  * The fkey is read with parse_fkey_ref(), because the store answers
75304
75585
  * in list_dict -- `[{topic_name, id, hook_name}]` -- and not in the
75305
75586
  * `topics^<topic id>^cols` strings this view writes. Split as a
75306
- * string, every production answer marked nothing (fourth independent
75307
- * review, probe C).
75587
+ * string, every production answer marked nothing.
75308
75588
  ***************************************************************/
75309
75589
  function mark_written_record(gobj, topic_name, record) {
75310
75590
  let priv = gobj.priv;
@@ -75361,7 +75641,7 @@ function ac_hide(gobj, event, kw, src) {
75361
75641
  *
75362
75642
  * Nothing else ends a load or a write the drop cut: their answers
75363
75643
  * will never come, and waiting in ST_LOADING skipped the reload
75364
- * itself (M-1 of the independent review of 7.25.4).
75644
+ * itself.
75365
75645
  ***************************************************************/
75366
75646
  function ac_transport_state(gobj, event, kw, src) {
75367
75647
  let priv = gobj.priv;
@@ -75392,6 +75672,21 @@ function ac_refresh$1(gobj, event, kw, src) {
75392
75672
  return request_model(gobj, false);
75393
75673
  }
75394
75674
  /***************************************************************
75675
+ * EV_REFRESH while writes are in flight: the view's own Refresh
75676
+ * is busy then, so it is a host's (after a Save, say).
75677
+ *
75678
+ * Run when the writes END (end_writes()), not now. Run now, the
75679
+ * load replaced ST_SAVING and what was still queued was never
75680
+ * sent, with no word: nothing but the end of what is in flight
75681
+ * moves this view out of ST_LOADING or ST_SAVING.
75682
+ ***************************************************************/
75683
+ function ac_refresh_after_the_writes(gobj, event, kw, src) {
75684
+ let priv = gobj.priv;
75685
+ log_warning(`${gobj_short_name(gobj)}: ${event} while writes are in flight: the schemas are read again when the writes end`);
75686
+ priv.refresh_after_write = true;
75687
+ return 0;
75688
+ }
75689
+ /***************************************************************
75395
75690
  * What a DIALOG sends while the model is being read again: the
75396
75691
  * Save of a form left open when the reload started, a
75397
75692
  * confirmation, an import. The loading screen took the rest of
@@ -75400,12 +75695,11 @@ function ac_refresh$1(gobj, event, kw, src) {
75400
75695
  * a model the answers in the air are about to replace.
75401
75696
  *
75402
75697
  * Refused, and SAID. Undeclared, it answered "Event NOT DEFINED
75403
- * in state ST_LOADING" and the edit was gone with no word (third
75404
- * independent review).
75698
+ * in state ST_LOADING" and the edit was gone with no word.
75405
75699
  *
75406
75700
  * And NOT "try again": the same Save, once the load was in, sent
75407
75701
  * the form built on the old model -- every field of the old record
75408
- * over the newer one (fourth independent review). The load that
75702
+ * over the newer one. The load that
75409
75703
  * lands closes such a dialog (end_load()); one that fails leaves it
75410
75704
  * up, on the model it was built on.
75411
75705
  ***************************************************************/
@@ -75419,8 +75713,7 @@ function ac_wait_for_the_load(gobj, event, kw, src) {
75419
75713
  * reload landed on zero treedbs while it was up) or ST_IDLE (no
75420
75714
  * model). A confirmation is a shell modal, not this view's dialog,
75421
75715
  * so the load that lands does not close it, and its Yes arrives
75422
- * anyway. Undeclared, it answered "Event NOT DEFINED" (fifth
75423
- * independent review).
75716
+ * anyway. Undeclared, it answered "Event NOT DEFINED".
75424
75717
  *
75425
75718
  * Refused with the stamp's own words: whatever it was decided on
75426
75719
  * is not on screen any more.
@@ -75436,13 +75729,12 @@ function ac_confirmed_on_nothing(gobj, event, kw, src) {
75436
75729
  * topics hold a draft in __system__ that is not saved (C_TREEDB's
75437
75730
  * saved-schema, `draft_changed`). The mark of a write lived in this
75438
75731
  * session's memory only, so a reload of the page, a reconnect or a
75439
- * refresh showed no draft while __system__ still differed (N13 of
75440
- * the 2026-09-22 review).
75732
+ * refresh showed no draft while __system__ still differed.
75441
75733
  *
75442
75734
  * REPLACES what the host said before -- the host is the truth after
75443
75735
  * a reload, and a Save is exactly the host saying "none any more".
75444
75736
  * It only ever added, and a topic stayed a draft after the Save that
75445
- * published it (M1 of the 2026-09-23 review). What this session wrote
75737
+ * published it. What this session wrote
75446
75738
  * stays marked: it is kept apart (see topic_is_draft()).
75447
75739
  ***************************************************************/
75448
75740
  function ac_drafts(gobj, event, kw, src) {
@@ -75499,7 +75791,10 @@ function ac_show_orphans(gobj, event, kw, src) {
75499
75791
  * to feed.
75500
75792
  ***************************************************************/
75501
75793
  function confirm_then(gobj, message, detail, kw) {
75502
- kw = Object.assign({ model_gen: gobj.priv.model_gen }, kw);
75794
+ kw = Object.assign({
75795
+ model_gen: gobj.priv.model_gen,
75796
+ seg: position_seg(gobj)
75797
+ }, kw);
75503
75798
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
75504
75799
  t,
75505
75800
  detail,
@@ -75566,7 +75861,13 @@ function ac_save_column(gobj, event, kw, src) {
75566
75861
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
75567
75862
  let col = kw.creating ? null : find_col(priv.model, priv.treedb_id, kw.topic, kw.col);
75568
75863
  if (refuse_if_readonly(gobj, event)) return -1;
75569
- if (reload_first(gobj, event)) return -1;
75864
+ if (reload_first(gobj, event, {
75865
+ form: "column",
75866
+ topic: kw.topic,
75867
+ col: kw.col,
75868
+ creating: !!kw.creating,
75869
+ changed: kw.changed || {}
75870
+ })) return -1;
75570
75871
  if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75571
75872
  if (!topic || !kw.creating && !col) {
75572
75873
  log_error(`${gobj_short_name(gobj)}: ${event} names no column: ${kw.topic}.${kw.col}`);
@@ -75694,7 +75995,13 @@ function ac_save_topic(gobj, event, kw, src) {
75694
75995
  let treedb = current_treedb(gobj);
75695
75996
  let topic = kw.creating ? null : find_topic(priv.model, priv.treedb_id, kw.topic);
75696
75997
  if (refuse_if_readonly(gobj, event)) return -1;
75697
- if (reload_first(gobj, event)) return -1;
75998
+ if (reload_first(gobj, event, {
75999
+ form: "topic",
76000
+ topic: kw.topic,
76001
+ col: "",
76002
+ creating: !!kw.creating,
76003
+ changed: kw.changed || {}
76004
+ })) return -1;
75698
76005
  if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75699
76006
  if (!treedb || !kw.creating && !topic) {
75700
76007
  log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
@@ -75885,6 +76192,7 @@ function ac_confirmed(gobj, event, kw, src) {
75885
76192
  if (refuse_if_readonly(gobj, event)) return -1;
75886
76193
  if (reload_first(gobj, event)) return -1;
75887
76194
  if (built_on_a_replaced_model(gobj, event, kw)) return -1;
76195
+ if (asked_on_another_screen(gobj, event, kw)) return -1;
75888
76196
  if (kw.what === "import") return run_import(gobj);
75889
76197
  if (kw.what === "topic") {
75890
76198
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
@@ -76156,7 +76464,11 @@ function create_gclass$1(gclass_name) {
76156
76464
  null
76157
76465
  ]
76158
76466
  ])],
76159
- ["ST_SAVING", COMMON.slice()]
76467
+ ["ST_SAVING", COMMON.filter((a) => a[0] !== "EV_REFRESH").concat([[
76468
+ "EV_REFRESH",
76469
+ ac_refresh_after_the_writes,
76470
+ null
76471
+ ]])]
76160
76472
  ];
76161
76473
  const event_types = [
76162
76474
  ["EV_MT_COMMAND_ANSWER", event_flag_t.EVF_PUBLIC_EVENT],