@yuneta/gobj-ui 7.25.8 → 7.25.9

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
@@ -2182,12 +2182,40 @@ else.
2182
2182
  `ST_LOADING` declares none of a screen's actions. A **dialog** left open is
2183
2183
  the one thing still reachable: its Save (`EV_SAVE_COLUMN`, `EV_SAVE_TOPIC`),
2184
2184
  a confirmation (`EV_CONFIRMED`), an orphan delete or an import is refused
2185
- with `the schemas are loading: try again when they are in` (an i18n key the
2186
- consumer's locales carry) and the dialog stays open with what was typed, so
2187
- the same Save works once the load is in. A write that ends with an error --
2188
- a drop included -- still applies a position that waited, as a load does.
2189
- Out of session a load is not asked at all (no *"not in session"* errors);
2190
- the reconnect loads.
2185
+ with `the schemas are loading: wait for them` (7.25.9; an i18n key the
2186
+ consumer's locales carry). A write that ends with an error -- a drop
2187
+ included -- still applies a position that waited, as a load does. Out of
2188
+ session a load is not asked at all (no *"not in session"* errors); the
2189
+ reconnect loads.
2190
+ - **A dialog is built on ONE model, and a load that replaces it closes it**
2191
+ (7.25.9). Every load that lands bumps `model_gen`; a dialog records the one
2192
+ it was opened on, and its Save, its import Preview/Run, an orphan delete and
2193
+ a confirmation carry it in their kw. When a load lands under a dialog of an
2194
+ older model, the dialog is closed and the operator told
2195
+ `the schemas were read again: open the dialog again`; anything stamped with
2196
+ a replaced model that arrives anyway is refused with the same words. A
2197
+ column form sends every field it shows, so its Save after a reload wrote
2198
+ the OLD record over the newer one -- 7.25.8 told the operator to "try
2199
+ again", which did exactly that. The import plan is forgotten when a load
2200
+ starts. A load that failed or never left replaced nothing, and the dialog
2201
+ stays with what was typed.
2202
+
2203
+ ```js
2204
+ gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host); // form, model_gen N
2205
+ gobj_send_event(editor, "EV_REFRESH", {}, host); // Save -> "wait for them"
2206
+ // ... the load lands (model_gen N+1): the form closes, the operator reopens it
2207
+ ```
2208
+ - **A load refused IN session keeps the model it was replacing** (7.25.9): a
2209
+ routing adapter's deadline on a slow `nodes` answers -1 while the session
2210
+ is up. The records are put back, the screen and any open dialog stay, the
2211
+ operator is told `cannot read the schemas again: the previous ones stay`,
2212
+ and the reload is owed. Only a first load, with no model before it, ends
2213
+ on the empty screen. A write answered with no record reloads the model and
2214
+ says what it did not send after it
2215
+ (`the treedb did not describe a write back: the writes after it were not sent`).
2216
+ - The toolbar offers Diagram, Check, Export, Import, New topic and New column
2217
+ only when the treedb (or topic) the position names is IN the model; after a
2218
+ reload that no longer has it, Back and Refresh remain.
2191
2219
  - A write this view gave up on (a drop, a deadline) whose answer still says it
2192
2220
  was DONE makes the editor read the model again (at once, after the write in
2193
2221
  flight, or on the reconnect), and it is a write for the host too: it
@@ -3062,6 +3062,8 @@ function show_notification(shell, kind, message, opts) {
3062
3062
  },
3063
3063
  [["button", {
3064
3064
  class: "TOAST_CLOSE delete",
3065
+ title: (0, i18next.t)("close"),
3066
+ "data-i18n-title": "close",
3065
3067
  "aria-label": (0, i18next.t)("close"),
3066
3068
  "data-i18n-aria-label": "close"
3067
3069
  }], [
@@ -3185,6 +3187,8 @@ function yui_shell_show_modal(shell, content, opts) {
3185
3187
  {
3186
3188
  class: "MODAL_CLOSE yui-dialog-x",
3187
3189
  type: "button",
3190
+ title: (0, i18next.t)("close"),
3191
+ "data-i18n-title": "close",
3188
3192
  "aria-label": (0, i18next.t)("close"),
3189
3193
  "data-i18n-aria-label": "close"
3190
3194
  },
@@ -3206,6 +3210,8 @@ function yui_shell_show_modal(shell, content, opts) {
3206
3210
  ]];
3207
3211
  if (with_close) modal_children.push(["button", {
3208
3212
  class: "MODAL_CLOSE modal-close is-large",
3213
+ title: (0, i18next.t)("close"),
3214
+ "data-i18n-title": "close",
3209
3215
  "aria-label": (0, i18next.t)("close"),
3210
3216
  "data-i18n-aria-label": "close"
3211
3217
  }]);
@@ -3360,6 +3366,8 @@ function build_dialog(shell, message, buttons, opts) {
3360
3366
  ]);
3361
3367
  $card_children.push(["button", {
3362
3368
  class: "CONFIRM_CLOSE delete yui-confirm-x",
3369
+ title: (0, i18next.t)("close"),
3370
+ "data-i18n-title": "close",
3363
3371
  "aria-label": (0, i18next.t)("close"),
3364
3372
  "data-i18n-aria-label": "close"
3365
3373
  }], [
@@ -72173,7 +72181,9 @@ var attrs_table$1 = [
72173
72181
  ];
72174
72182
  var PRIVATE_DATA$1 = {
72175
72183
  records: null,
72184
+ records_before: null,
72176
72185
  model: null,
72186
+ model_gen: 0,
72177
72187
  pending: 0,
72178
72188
  load_error: "",
72179
72189
  load_round: 0,
@@ -72188,6 +72198,7 @@ var PRIVATE_DATA$1 = {
72188
72198
  order_undo: null,
72189
72199
  diagram_gobj: null,
72190
72200
  dialog: null,
72201
+ dialog_gen: 0,
72191
72202
  save_queue: null,
72192
72203
  save_return: "",
72193
72204
  save_wrote: false,
@@ -72348,6 +72359,29 @@ function refuse_if_readonly(gobj, event) {
72348
72359
  yui_shell_show_error(yui_shell_of(gobj), "the treedb is read-only", { t: i18next.t });
72349
72360
  return true;
72350
72361
  }
72362
+ /***************************************************************
72363
+ * A decision taken on a model a load has since REPLACED: the Save
72364
+ * of a form, a confirmation, an import. Each carries the
72365
+ * `model_gen` it was built on (a number, not the model: a kw is
72366
+ * dumped by the machine trace).
72367
+ *
72368
+ * Refused, and said. A column form sends every field it shows, so
72369
+ * its Save after a reload wrote the OLD record over the newer one
72370
+ * (fourth independent review, probe A3); a form whose column the
72371
+ * reload removed answered "names no column" for ever. end_load()
72372
+ * closes such a dialog when the load lands; this is the same rule
72373
+ * for whatever arrives anyway -- a stale screen, a confirmation
72374
+ * that was up, a keyboard.
72375
+ *
72376
+ * A kw with no stamp is not a dialog's and is not asked.
72377
+ ***************************************************************/
72378
+ function built_on_a_replaced_model(gobj, event, kw) {
72379
+ let priv = gobj.priv;
72380
+ if (!kw || kw.model_gen === void 0 || kw.model_gen === priv.model_gen) return false;
72381
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, it was decided on the schemas a reload replaced`);
72382
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again: open the dialog again", { t: i18next.t });
72383
+ return true;
72384
+ }
72351
72385
  /***************************
72352
72386
  * The model
72353
72387
  ***************************/
@@ -72362,7 +72396,11 @@ function refuse_if_readonly(gobj, event) {
72362
72396
  * and the toolbar stops answering: left up, a click on a card, on
72363
72397
  * Back or on the drawing answered "Event NOT DEFINED in state
72364
72398
  * ST_LOADING" and was lost (third independent review). A dialog
72365
- * left open is answered in ST_LOADING instead (ac_wait_for_the_load).
72399
+ * left open is answered in ST_LOADING instead (ac_wait_for_the_load),
72400
+ * and a load that LANDS closes it (end_load()): it was built on the
72401
+ * model the load replaced, and its Save sends every field it shows.
72402
+ * An import plan is forgotten here for the same reason: it is a list
72403
+ * of writes computed against the model going away.
72366
72404
  *
72367
72405
  * `keep_written`: a load the host did not ask for -- the reconnect,
72368
72406
  * a write that turned out done -- keeps what this session wrote.
@@ -72372,12 +72410,16 @@ function refuse_if_readonly(gobj, event) {
72372
72410
  * is not even asked): the records the model was built on are put
72373
72411
  * back (emptied, the next write patched them into a model with no
72374
72412
  * treedb), the round is spent so a request that did leave is
72375
- * stale, and the session back asks again.
72413
+ * stale, and the session back asks again. A load that left and
72414
+ * FAILED keeps them the same way (`records_before`,
72415
+ * ac_mt_command_answer()).
72376
72416
  ***************************************************************/
72377
72417
  function request_model(gobj, keep_written) {
72378
72418
  let priv = gobj.priv;
72379
72419
  if (!(0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno")) return -1;
72420
+ forget_import_plan(gobj);
72380
72421
  let previous = priv.records;
72422
+ priv.records_before = previous;
72381
72423
  priv.records = {
72382
72424
  treedbs: [],
72383
72425
  topics: [],
@@ -72403,6 +72445,7 @@ function request_model(gobj, keep_written) {
72403
72445
  }
72404
72446
  if (left < 3) {
72405
72447
  priv.records = previous;
72448
+ priv.records_before = null;
72406
72449
  priv.load_round++;
72407
72450
  priv.reload_on_open = true;
72408
72451
  priv.load_error = (0, i18next.t)("cannot reach the treedb");
@@ -72498,9 +72541,10 @@ function start_measuring(gobj) {
72498
72541
  * until the host saves it (C_TREEDB's save-schema, which raises the
72499
72542
  * versions of what changed). Two sources say a topic is one, and
72500
72543
  * they are kept APART because they are forgotten differently:
72501
- * - `written`: what THIS session wrote since the model was loaded
72502
- * (a reload, the host's EV_REFRESH, forgets it -- the store says
72503
- * what is left);
72544
+ * - `written`: what THIS session wrote. Only a load the HOST asked
72545
+ * for forgets it (its EV_REFRESH, which a Save sends); the
72546
+ * reconnect's reload, the one owed after a write and a late
72547
+ * write's keep it (start_measuring(), request_model());
72504
72548
  * - `host_ids`: what the host said last (EV_DRAFTS), replaced WHOLE
72505
72549
  * by the next EV_DRAFTS. Folded into `written`, a topic the host
72506
72550
  * named once stayed a draft after the Save that published it.
@@ -72668,6 +72712,8 @@ function render_toolbar(gobj) {
72668
72712
  let readonly = is_readonly(gobj);
72669
72713
  let has_model = !!priv.model;
72670
72714
  let on_a_treedb = state !== "ST_IDLE" && state !== "ST_EMPTY";
72715
+ let treedb_here = on_a_treedb && !!current_treedb(gobj);
72716
+ let topic_here = treedb_here && !!current_topic(gobj);
72671
72717
  let in_flight = state === "ST_SAVING" || state === "ST_LOADING";
72672
72718
  let $left = [];
72673
72719
  if (priv.treedb_id && on_a_treedb) $left.push([
@@ -72743,17 +72789,17 @@ function render_toolbar(gobj) {
72743
72789
  $trail
72744
72790
  ]);
72745
72791
  let $right = [];
72746
- if (priv.treedb_id && has_model && on_a_treedb) {
72792
+ if (priv.treedb_id && has_model && treedb_here) {
72747
72793
  $right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-compass-drafting", priv.diagram ? "topics" : "diagram", "EV_TOGGLE_DIAGRAM", false));
72748
72794
  $right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-square-check", "check", "EV_VALIDATE", false));
72749
72795
  $right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-download", "export", "EV_EXPORT", false));
72750
72796
  if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-upload", "import", "EV_IMPORT", false));
72751
72797
  }
72752
- if (priv.topic_name && !readonly && !priv.diagram && on_a_treedb) {
72798
+ if (priv.topic_name && !readonly && !priv.diagram && topic_here) {
72753
72799
  if (undo_order_writes(gobj, current_topic(gobj)).length > 0) $right.push(toolbar_button("SCHEMA_UNDO_ORDER_BTN", "yi-arrow-rotate-left", "undo the order", "EV_UNDO_ORDER", false));
72754
72800
  $right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus", "new column", "EV_ADD_COLUMN", false));
72755
72801
  }
72756
- if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram && on_a_treedb) $right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus", "new topic", "EV_ADD_TOPIC", false));
72802
+ if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram && treedb_here) $right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus", "new topic", "EV_ADD_TOPIC", false));
72757
72803
  $right.push(toolbar_button("SCHEMA_REFRESH_BTN", "yi-arrows-rotate", "refresh", "EV_REFRESH", state === "ST_LOADING"));
72758
72804
  let $bar = (0, _yuneta_gobj_js.createElement2)([
72759
72805
  "div",
@@ -73573,6 +73619,7 @@ function close_dialog(gobj) {
73573
73619
  function open_dialog(gobj, $content, title, logical, title_prefix) {
73574
73620
  let priv = gobj.priv;
73575
73621
  close_dialog(gobj);
73622
+ priv.dialog_gen = priv.model_gen;
73576
73623
  priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
73577
73624
  dialog: true,
73578
73625
  title,
@@ -73619,11 +73666,28 @@ function name_control($spec, label) {
73619
73666
  $spec[1]["aria-label"] = (0, i18next.t)(label);
73620
73667
  $spec[1]["data-i18n-aria-label"] = label;
73621
73668
  }
73669
+ if (!$spec[1].title) {
73670
+ $spec[1].title = (0, i18next.t)(label);
73671
+ $spec[1]["data-i18n-title"] = label;
73672
+ }
73622
73673
  return true;
73623
73674
  }
73624
73675
  for (let child of $spec[2] || []) if (name_control(child, label)) return true;
73625
73676
  return false;
73626
73677
  }
73678
+ /***************************************************************
73679
+ * The four attributes that NAME a control -- a title and an
73680
+ * aria-label, each with the key a language change re-translates
73681
+ * it by. A visible label is not a name (CLAUDE.md).
73682
+ ***************************************************************/
73683
+ function name_attrs(key, attrs) {
73684
+ return Object.assign({
73685
+ title: (0, i18next.t)(key),
73686
+ "data-i18n-title": key,
73687
+ "aria-label": (0, i18next.t)(key),
73688
+ "data-i18n-aria-label": key
73689
+ }, attrs);
73690
+ }
73627
73691
  function field(logical, name, label, $control, help) {
73628
73692
  name_control($control, label);
73629
73693
  return [
@@ -73800,7 +73864,7 @@ function read_flags($root) {
73800
73864
  * than not offering it.
73801
73865
  ***************************************************************/
73802
73866
  function open_column_form(gobj, topic, col, prefill) {
73803
- gobj.priv;
73867
+ let priv = gobj.priv;
73804
73868
  let record = prefill || col && col.record || {};
73805
73869
  let creating = !col;
73806
73870
  let type = record.type || "string";
@@ -73810,6 +73874,7 @@ function open_column_form(gobj, topic, col, prefill) {
73810
73874
  let hook_col = hook_topic ? hook[hook_topic] : "";
73811
73875
  let treedb = current_treedb(gobj);
73812
73876
  let sibling_topics = treedb ? treedb.topics.map((x) => x.name) : [];
73877
+ let model_gen = priv.model_gen;
73813
73878
  let $form = (0, _yuneta_gobj_js.createElement2)([
73814
73879
  "div",
73815
73880
  { class: "SCHEMA_COL_FORM box" },
@@ -73872,10 +73937,10 @@ function open_column_form(gobj, topic, col, prefill) {
73872
73937
  },
73873
73938
  [[
73874
73939
  "button",
73875
- {
73940
+ name_attrs("cancel", {
73876
73941
  class: "SCHEMA_COL_FORM_CANCEL button",
73877
73942
  type: "button"
73878
- },
73943
+ }),
73879
73944
  [[
73880
73945
  "span",
73881
73946
  { i18n: "cancel" },
@@ -73883,10 +73948,10 @@ function open_column_form(gobj, topic, col, prefill) {
73883
73948
  ]]
73884
73949
  ], [
73885
73950
  "button",
73886
- {
73951
+ name_attrs("save", {
73887
73952
  class: "SCHEMA_COL_FORM_SAVE button is-primary",
73888
73953
  type: "button"
73889
- },
73954
+ }),
73890
73955
  [[
73891
73956
  "span",
73892
73957
  { class: "icon" },
@@ -73926,7 +73991,8 @@ function open_column_form(gobj, topic, col, prefill) {
73926
73991
  topic: topic.name,
73927
73992
  col: creating ? "" : col.name,
73928
73993
  creating,
73929
- values
73994
+ values,
73995
+ model_gen
73930
73996
  }, gobj);
73931
73997
  });
73932
73998
  open_dialog(gobj, $form, creating ? "new column" : "edit column", "SCHEMA_COL_FORM_DIALOG", creating ? topic.name : `${topic.name}.${col.name}`);
@@ -73949,6 +74015,7 @@ function open_topic_form(gobj, treedb, topic) {
73949
74015
  let record = topic && topic.record || {};
73950
74016
  let creating = !topic;
73951
74017
  let col_names = topic ? topic.cols.map((c) => c.name) : [];
74018
+ let model_gen = gobj.priv.model_gen;
73952
74019
  let $form = (0, _yuneta_gobj_js.createElement2)([
73953
74020
  "div",
73954
74021
  { class: "SCHEMA_TOPIC_FORM box" },
@@ -74007,10 +74074,10 @@ function open_topic_form(gobj, treedb, topic) {
74007
74074
  },
74008
74075
  [[
74009
74076
  "button",
74010
- {
74077
+ name_attrs("cancel", {
74011
74078
  class: "SCHEMA_TOPIC_FORM_CANCEL button",
74012
74079
  type: "button"
74013
- },
74080
+ }),
74014
74081
  [[
74015
74082
  "span",
74016
74083
  { i18n: "cancel" },
@@ -74018,10 +74085,10 @@ function open_topic_form(gobj, treedb, topic) {
74018
74085
  ]]
74019
74086
  ], [
74020
74087
  "button",
74021
- {
74088
+ name_attrs("save", {
74022
74089
  class: "SCHEMA_TOPIC_FORM_SAVE button is-primary",
74023
74090
  type: "button"
74024
- },
74091
+ }),
74025
74092
  [[
74026
74093
  "span",
74027
74094
  { class: "icon" },
@@ -74044,7 +74111,8 @@ function open_topic_form(gobj, treedb, topic) {
74044
74111
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SAVE_TOPIC", {
74045
74112
  topic: creating ? "" : topic.name,
74046
74113
  creating,
74047
- values: read_form($form)
74114
+ values: read_form($form),
74115
+ model_gen
74048
74116
  }, gobj);
74049
74117
  });
74050
74118
  open_dialog(gobj, $form, creating ? "new topic" : "edit topic", "SCHEMA_TOPIC_FORM_DIALOG", creating ? treedb.id : `${treedb.id}.${topic.name}`);
@@ -74324,6 +74392,7 @@ function copy_text(gobj, $text) {
74324
74392
  * plan itself, so what is confirmed is what runs.
74325
74393
  ***************************************************************/
74326
74394
  function open_import(gobj, treedb) {
74395
+ let model_gen = gobj.priv.model_gen;
74327
74396
  let $content = (0, _yuneta_gobj_js.createElement2)([
74328
74397
  "div",
74329
74398
  { class: "SCHEMA_IMPORT box" },
@@ -74338,12 +74407,12 @@ function open_import(gobj, treedb) {
74338
74407
  ],
74339
74408
  [
74340
74409
  "textarea",
74341
- {
74410
+ name_attrs("schema", {
74342
74411
  class: "SCHEMA_IMPORT_TEXT textarea is-small is-family-monospace",
74343
74412
  rows: "12",
74344
74413
  spellcheck: "false",
74345
74414
  placeholder: "{\"id\": \"...\", \"schema_version\": \"1\", \"topics\": []}"
74346
- },
74415
+ }),
74347
74416
  ""
74348
74417
  ],
74349
74418
  [
@@ -74376,10 +74445,10 @@ function open_import(gobj, treedb) {
74376
74445
  [
74377
74446
  [
74378
74447
  "button",
74379
- {
74448
+ name_attrs("cancel", {
74380
74449
  class: "SCHEMA_IMPORT_CANCEL button",
74381
74450
  type: "button"
74382
- },
74451
+ }),
74383
74452
  [[
74384
74453
  "span",
74385
74454
  { i18n: "cancel" },
@@ -74388,10 +74457,10 @@ function open_import(gobj, treedb) {
74388
74457
  ],
74389
74458
  [
74390
74459
  "button",
74391
- {
74460
+ name_attrs("preview", {
74392
74461
  class: "SCHEMA_IMPORT_PREVIEW button",
74393
74462
  type: "button"
74394
- },
74463
+ }),
74395
74464
  [[
74396
74465
  "span",
74397
74466
  { class: "icon" },
@@ -74404,11 +74473,11 @@ function open_import(gobj, treedb) {
74404
74473
  ],
74405
74474
  [
74406
74475
  "button",
74407
- {
74476
+ name_attrs("import", {
74408
74477
  class: "SCHEMA_IMPORT_RUN button is-warning",
74409
74478
  type: "button",
74410
74479
  disabled: "disabled"
74411
- },
74480
+ }),
74412
74481
  [[
74413
74482
  "span",
74414
74483
  { class: "icon" },
@@ -74435,18 +74504,31 @@ function open_import(gobj, treedb) {
74435
74504
  evt.stopPropagation();
74436
74505
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_PREVIEW_IMPORT", {
74437
74506
  text: $text.value,
74438
- prune: $prune.checked
74507
+ prune: $prune.checked,
74508
+ model_gen
74439
74509
  }, gobj);
74440
74510
  });
74441
74511
  $run.addEventListener("click", (evt) => {
74442
74512
  evt.stopPropagation();
74443
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_APPLY_IMPORT", {}, gobj);
74513
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_APPLY_IMPORT", { model_gen }, gobj);
74444
74514
  });
74445
74515
  gobj.priv.import_pane = $plan_pane;
74446
74516
  gobj.priv.import_run = $run;
74447
74517
  open_dialog(gobj, $content, "import", "SCHEMA_IMPORT_DIALOG", treedb.id);
74448
74518
  }
74449
74519
  /***************************************************************
74520
+ * The plan is a list of writes computed against the model shown:
74521
+ * a load replaces the model, so the plan goes with it and the
74522
+ * dialog, if it stays up (a load that failed keeps the model),
74523
+ * asks for a Preview again.
74524
+ ***************************************************************/
74525
+ function forget_import_plan(gobj) {
74526
+ let priv = gobj.priv;
74527
+ priv.import_plan = null;
74528
+ clear(priv.import_pane);
74529
+ if (priv.import_run) priv.import_run.disabled = true;
74530
+ }
74531
+ /***************************************************************
74450
74532
  * The plan, drawn where the paste is: one line per write, the
74451
74533
  * deletions apart and counted, because they are what the
74452
74534
  * operator is being asked about.
@@ -74573,7 +74655,8 @@ function open_orphans(gobj) {
74573
74655
  { class: "SCHEMA_ORPHAN_ACTIONS has-text-right" },
74574
74656
  readonly ? [] : [row_icon(gobj, "SCHEMA_ORPHAN_DELETE", "yi-trash", "delete", "EV_DELETE_ORPHAN", {
74575
74657
  kind,
74576
- id: entry.id
74658
+ id: entry.id,
74659
+ model_gen: priv.model_gen
74577
74660
  })]
74578
74661
  ]
74579
74662
  ]
@@ -74745,6 +74828,10 @@ function transport_dropped(gobj) {
74745
74828
  priv.load_round++;
74746
74829
  priv.pending = 0;
74747
74830
  priv.load_error = "";
74831
+ if (priv.records_before) {
74832
+ priv.records = priv.records_before;
74833
+ priv.records_before = null;
74834
+ }
74748
74835
  return end_load(gobj);
74749
74836
  }
74750
74837
  return 0;
@@ -74755,11 +74842,22 @@ function transport_dropped(gobj) {
74755
74842
  * in flight (ac_show() kept it), or the one it had. Without a
74756
74843
  * model there is no screen to go to: the position is kept for the
74757
74844
  * next load, and the editor says why it is empty.
74845
+ *
74846
+ * A dialog built on the model this load REPLACED is closed, and the
74847
+ * operator told to open it again: it shows the old record, and its
74848
+ * Save would write every field of it over the newer one. A load
74849
+ * that failed or never left replaced nothing (`model_gen` did not
74850
+ * move), and the dialog stays with what was typed.
74758
74851
  ***************************************************************/
74759
74852
  function end_load(gobj) {
74760
74853
  let priv = gobj.priv;
74761
74854
  let seg = priv.pending_seg;
74762
74855
  priv.pending_seg = null;
74856
+ if (priv.dialog && priv.dialog_gen !== priv.model_gen) {
74857
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: a dialog was open on the schemas the load replaced: closed`);
74858
+ close_dialog(gobj);
74859
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again: open the dialog again", { t: i18next.t });
74860
+ }
74763
74861
  if (priv.model) {
74764
74862
  if (seg !== null) return apply_seg(gobj, seg);
74765
74863
  return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
@@ -74975,10 +75073,20 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74975
75073
  else if (priv.records && Array.isArray(priv.records[topic_name])) priv.records[topic_name] = Array.isArray(data) ? data : [];
74976
75074
  if (priv.pending > 0) return 0;
74977
75075
  if (priv.load_error) {
75076
+ if (priv.model) {
75077
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the schemas could not be read again (${priv.load_error}): the previous ones stay`);
75078
+ priv.records = priv.records_before;
75079
+ priv.records_before = null;
75080
+ priv.reload_on_open = true;
75081
+ yui_shell_show_error(yui_shell_of(gobj), "cannot read the schemas again: the previous ones stay", { t: i18next.t });
75082
+ return end_load(gobj);
75083
+ }
74978
75084
  priv.model = null;
74979
75085
  return end_load(gobj);
74980
75086
  }
74981
75087
  priv.order_undo = {};
75088
+ priv.records_before = null;
75089
+ priv.model_gen++;
74982
75090
  build_model(gobj);
74983
75091
  start_measuring(gobj);
74984
75092
  return end_load(gobj);
@@ -75000,11 +75108,14 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75000
75108
  let record = (0, _yuneta_gobj_js.is_object)(data) ? data : Array.isArray(data) && (0, _yuneta_gobj_js.is_object)(data[0]) ? data[0] : null;
75001
75109
  if (record) patch_record(gobj, topic_name, record);
75002
75110
  else {
75003
- (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' answered no record`);
75111
+ let not_sent = priv.save_queue.length - 1;
75112
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' answered no record` + (not_sent > 0 ? `: ${not_sent} write(s) after it were not sent` : ""));
75113
+ if (not_sent > 0) yui_shell_show_error(yui_shell_of(gobj), "the treedb did not describe a write back: the writes after it were not sent", { t: i18next.t });
75004
75114
  if (topic_name === T_TOPICS || topic_name === T_COLS) {
75005
75115
  let write = priv.save_queue[0];
75006
75116
  mark_written(gobj, topic_name, write ? write.record : null);
75007
75117
  }
75118
+ priv.reload_after_write = false;
75008
75119
  priv.save_queue = [];
75009
75120
  priv.save_return = "";
75010
75121
  priv.save_wrote = false;
@@ -75062,8 +75173,14 @@ function late_write_done(gobj, command, topic_name, data) {
75062
75173
  /***************************************************************
75063
75174
  * The topic a record written belongs to, read off the RECORD and
75064
75175
  * not off the screen: a write answered late may be about a topic
75065
- * the operator has left. A column names its topic in its fkey
75066
- * (`topics^<topic id>^cols`); a topic is its own id.
75176
+ * the operator has left. A column names its topic in its fkey; a
75177
+ * topic is its own id.
75178
+ *
75179
+ * The fkey is read with parse_fkey_ref(), because the store answers
75180
+ * in list_dict -- `[{topic_name, id, hook_name}]` -- and not in the
75181
+ * `topics^<topic id>^cols` strings this view writes. Split as a
75182
+ * string, every production answer marked nothing (fourth independent
75183
+ * review, probe C).
75067
75184
  ***************************************************************/
75068
75185
  function mark_written_record(gobj, topic_name, record) {
75069
75186
  let priv = gobj.priv;
@@ -75073,11 +75190,7 @@ function mark_written_record(gobj, topic_name, record) {
75073
75190
  return;
75074
75191
  }
75075
75192
  if (topic_name !== T_COLS) return;
75076
- let refs = Array.isArray(record.topics) ? record.topics : typeof record.topics === "string" ? [record.topics] : [];
75077
- for (let ref of refs) {
75078
- let parts = String(ref).split("^");
75079
- if (parts.length >= 2 && parts[1]) priv.written[parts[1]] = true;
75080
- }
75193
+ for (let ref of parse_fkey_ref(record.topics)) priv.written[ref.id] = true;
75081
75194
  }
75082
75195
  /***************************************************************
75083
75196
  * Remember which topic this write belongs to, which is what
@@ -75161,14 +75274,19 @@ function ac_refresh$1(gobj, event, kw, src) {
75161
75274
  * operator can still reach, and what it asks is computed against
75162
75275
  * a model the answers in the air are about to replace.
75163
75276
  *
75164
- * Refused, and SAID -- and the dialog stays open with what was
75165
- * typed, so the same Save works once the load is in. Undeclared,
75166
- * it answered "Event NOT DEFINED in state ST_LOADING" and the
75167
- * edit was gone with no word (third independent review).
75277
+ * Refused, and SAID. Undeclared, it answered "Event NOT DEFINED
75278
+ * in state ST_LOADING" and the edit was gone with no word (third
75279
+ * independent review).
75280
+ *
75281
+ * And NOT "try again": the same Save, once the load was in, sent
75282
+ * the form built on the old model -- every field of the old record
75283
+ * over the newer one (fourth independent review). The load that
75284
+ * lands closes such a dialog (end_load()); one that fails leaves it
75285
+ * up, on the model it was built on.
75168
75286
  ***************************************************************/
75169
75287
  function ac_wait_for_the_load(gobj, event, kw, src) {
75170
75288
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, the schemas are loading`);
75171
- yui_shell_show_error(yui_shell_of(gobj), "the schemas are loading: try again when they are in", { t: i18next.t });
75289
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas are loading: wait for them", { t: i18next.t });
75172
75290
  return -1;
75173
75291
  }
75174
75292
  /***************************************************************
@@ -75234,6 +75352,7 @@ function ac_show_orphans(gobj, event, kw, src) {
75234
75352
  * to feed.
75235
75353
  ***************************************************************/
75236
75354
  function confirm_then(gobj, message, detail, kw) {
75355
+ kw = Object.assign({ model_gen: gobj.priv.model_gen }, kw);
75237
75356
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
75238
75357
  t: i18next.t,
75239
75358
  detail
@@ -75295,6 +75414,7 @@ function ac_save_column(gobj, event, kw, src) {
75295
75414
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
75296
75415
  let col = kw.creating ? null : find_col(priv.model, priv.treedb_id, kw.topic, kw.col);
75297
75416
  if (refuse_if_readonly(gobj, event)) return -1;
75417
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75298
75418
  if (!topic || !kw.creating && !col) {
75299
75419
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no column: ${kw.topic}.${kw.col}`);
75300
75420
  return -1;
@@ -75416,6 +75536,7 @@ function ac_save_topic(gobj, event, kw, src) {
75416
75536
  let treedb = current_treedb(gobj);
75417
75537
  let topic = kw.creating ? null : find_topic(priv.model, priv.treedb_id, kw.topic);
75418
75538
  if (refuse_if_readonly(gobj, event)) return -1;
75539
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75419
75540
  if (!treedb || !kw.creating && !topic) {
75420
75541
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no topic: ${kw.topic}`);
75421
75542
  return -1;
@@ -75458,6 +75579,7 @@ function ac_delete_topic(gobj, event, kw, src) {
75458
75579
  ***************************************************************/
75459
75580
  function ac_delete_orphan(gobj, event, kw, src) {
75460
75581
  if (refuse_if_readonly(gobj, event)) return -1;
75582
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75461
75583
  if ((0, _yuneta_gobj_js.empty_string)(kw.id)) {
75462
75584
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names nothing`);
75463
75585
  return -1;
@@ -75514,6 +75636,7 @@ function ac_preview_import(gobj, event, kw, src) {
75514
75636
  let priv = gobj.priv;
75515
75637
  let treedb = current_treedb(gobj);
75516
75638
  let incoming = null;
75639
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75517
75640
  if (!treedb) {
75518
75641
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no treedb open`);
75519
75642
  return -1;
@@ -75541,6 +75664,7 @@ function ac_preview_import(gobj, event, kw, src) {
75541
75664
  function ac_apply_import(gobj, event, kw, src) {
75542
75665
  let plan = gobj.priv.import_plan;
75543
75666
  if (refuse_if_readonly(gobj, event)) return -1;
75667
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75544
75668
  if (!plan || plan.writes.length === 0) {
75545
75669
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no plan to run`);
75546
75670
  return -1;
@@ -75583,6 +75707,7 @@ function run_import(gobj) {
75583
75707
  function ac_confirmed(gobj, event, kw, src) {
75584
75708
  let priv = gobj.priv;
75585
75709
  if (refuse_if_readonly(gobj, event)) return -1;
75710
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75586
75711
  if (kw.what === "import") return run_import(gobj);
75587
75712
  if (kw.what === "topic") {
75588
75713
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);