@yuneta/gobj-ui 7.25.7 → 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.
@@ -3027,12 +3027,20 @@ function maybe_apply_translator($node, opts) {
3027
3027
  * Returns `{ close() }` so callers can dismiss programmatically.
3028
3028
  *
3029
3029
  * ONE toast per message on screen: a string message of the same
3030
- * kind as a toast still showing is not stacked under it -- that
3031
- * toast is kept, its time starts again, and its handle is
3032
- * returned. One close of a transport settles every request it
3033
- * cut, each view shows its failure, and the operator got a
3034
- * column of identical "the connection dropped". A message built
3035
- * of nodes is never merged: its text is not a key.
3030
+ * kind as a toast still showing is not stacked under it. One
3031
+ * close of a transport settles every request it cut, each view
3032
+ * shows its failure, and the operator got a column of identical
3033
+ * "the connection dropped". A message built of nodes is never
3034
+ * merged: its text is not a key.
3035
+ *
3036
+ * A repeat is still a CALLER of its own: it gets its own handle
3037
+ * and its own time, and the toast stays while any caller holds
3038
+ * it -- until every handle is closed or has timed out. Handed the
3039
+ * first caller's handle and timer, a repeat asking `timeout: 0`
3040
+ * was dismissed on the first one's timer, and closing one
3041
+ * caller's handle closed a toast another was still showing
3042
+ * (third independent review). The operator's ✕ closes it for
3043
+ * everybody.
3036
3044
  ***************************************************************/
3037
3045
  function show_notification(shell, kind, message, opts) {
3038
3046
  let $layer = notification_layer(shell);
@@ -3040,12 +3048,10 @@ function show_notification(shell, kind, message, opts) {
3040
3048
  (0, _yuneta_gobj_js.log_warning)("yui_shell_show_*: shell has no notification layer");
3041
3049
  return { close: () => {} };
3042
3050
  }
3051
+ let timeout = opts && opts.timeout != null ? opts.timeout : 5e3;
3043
3052
  let key = typeof message === "string" ? `${kind}\u0000${message}` : null;
3044
3053
  if (key !== null) {
3045
- for (let $shown of Array.from($layer.children)) if ($shown.__toast_key__ === key && $shown.__toast__) {
3046
- $shown.__toast__.restart();
3047
- return $shown.__toast__.handle;
3048
- }
3054
+ for (let $shown of Array.from($layer.children)) if ($shown.__toast_key__ === key && $shown.__toast__) return $shown.__toast__.hold(timeout);
3049
3055
  }
3050
3056
  let p_attrs = typeof message === "string" ? { i18n: message } : {};
3051
3057
  let $note = (0, _yuneta_gobj_js.createElement2)([
@@ -3056,6 +3062,8 @@ function show_notification(shell, kind, message, opts) {
3056
3062
  },
3057
3063
  [["button", {
3058
3064
  class: "TOAST_CLOSE delete",
3065
+ title: (0, i18next.t)("close"),
3066
+ "data-i18n-title": "close",
3059
3067
  "aria-label": (0, i18next.t)("close"),
3060
3068
  "data-i18n-aria-label": "close"
3061
3069
  }], [
@@ -3069,35 +3077,38 @@ function show_notification(shell, kind, message, opts) {
3069
3077
  ]);
3070
3078
  $layer.appendChild($note);
3071
3079
  maybe_apply_translator($note, opts);
3072
- let timeout_id = null;
3080
+ let holders = /* @__PURE__ */ new Set();
3073
3081
  let closed = false;
3074
- let close = function() {
3082
+ let close_toast = function() {
3075
3083
  if (closed) return;
3076
3084
  closed = true;
3077
- if (timeout_id) {
3078
- clearTimeout(timeout_id);
3079
- timeout_id = null;
3085
+ for (let holder of holders) if (holder.timeout_id) {
3086
+ clearTimeout(holder.timeout_id);
3087
+ holder.timeout_id = null;
3080
3088
  }
3089
+ holders.clear();
3081
3090
  if ($note.parentNode) $note.parentNode.removeChild($note);
3082
3091
  };
3083
- let $del = $note.querySelector(".delete");
3084
- if ($del) $del.addEventListener("click", close);
3085
- let timeout = opts && opts.timeout != null ? opts.timeout : 5e3;
3086
- let restart = function() {
3087
- if (timeout_id) {
3088
- clearTimeout(timeout_id);
3089
- timeout_id = null;
3090
- }
3091
- if (timeout > 0) timeout_id = setTimeout(close, timeout);
3092
+ let hold = function(ms) {
3093
+ let holder = { timeout_id: null };
3094
+ let release = function() {
3095
+ if (!holders.has(holder)) return;
3096
+ if (holder.timeout_id) {
3097
+ clearTimeout(holder.timeout_id);
3098
+ holder.timeout_id = null;
3099
+ }
3100
+ holders.delete(holder);
3101
+ if (holders.size === 0) close_toast();
3102
+ };
3103
+ holders.add(holder);
3104
+ if (ms > 0) holder.timeout_id = setTimeout(release, ms);
3105
+ return { close: release };
3092
3106
  };
3093
- restart();
3094
- let handle = { close };
3107
+ let $del = $note.querySelector(".delete");
3108
+ if ($del) $del.addEventListener("click", close_toast);
3095
3109
  $note.__toast_key__ = key;
3096
- $note.__toast__ = {
3097
- handle,
3098
- restart
3099
- };
3100
- return handle;
3110
+ $note.__toast__ = { hold };
3111
+ return hold(timeout);
3101
3112
  }
3102
3113
  function yui_shell_show_info(shell, message, opts) {
3103
3114
  return show_notification(shell, "info", message, opts);
@@ -3176,6 +3187,8 @@ function yui_shell_show_modal(shell, content, opts) {
3176
3187
  {
3177
3188
  class: "MODAL_CLOSE yui-dialog-x",
3178
3189
  type: "button",
3190
+ title: (0, i18next.t)("close"),
3191
+ "data-i18n-title": "close",
3179
3192
  "aria-label": (0, i18next.t)("close"),
3180
3193
  "data-i18n-aria-label": "close"
3181
3194
  },
@@ -3197,6 +3210,8 @@ function yui_shell_show_modal(shell, content, opts) {
3197
3210
  ]];
3198
3211
  if (with_close) modal_children.push(["button", {
3199
3212
  class: "MODAL_CLOSE modal-close is-large",
3213
+ title: (0, i18next.t)("close"),
3214
+ "data-i18n-title": "close",
3200
3215
  "aria-label": (0, i18next.t)("close"),
3201
3216
  "data-i18n-aria-label": "close"
3202
3217
  }]);
@@ -3351,6 +3366,8 @@ function build_dialog(shell, message, buttons, opts) {
3351
3366
  ]);
3352
3367
  $card_children.push(["button", {
3353
3368
  class: "CONFIRM_CLOSE delete yui-confirm-x",
3369
+ title: (0, i18next.t)("close"),
3370
+ "data-i18n-title": "close",
3354
3371
  "aria-label": (0, i18next.t)("close"),
3355
3372
  "data-i18n-aria-label": "close"
3356
3373
  }], [
@@ -72164,10 +72181,13 @@ var attrs_table$1 = [
72164
72181
  ];
72165
72182
  var PRIVATE_DATA$1 = {
72166
72183
  records: null,
72184
+ records_before: null,
72167
72185
  model: null,
72186
+ model_gen: 0,
72168
72187
  pending: 0,
72169
72188
  load_error: "",
72170
72189
  load_round: 0,
72190
+ load_keeps_written: false,
72171
72191
  treedb_id: "",
72172
72192
  topic_name: "",
72173
72193
  diagram: false,
@@ -72178,6 +72198,7 @@ var PRIVATE_DATA$1 = {
72178
72198
  order_undo: null,
72179
72199
  diagram_gobj: null,
72180
72200
  dialog: null,
72201
+ dialog_gen: 0,
72181
72202
  save_queue: null,
72182
72203
  save_return: "",
72183
72204
  save_wrote: false,
@@ -72338,6 +72359,29 @@ function refuse_if_readonly(gobj, event) {
72338
72359
  yui_shell_show_error(yui_shell_of(gobj), "the treedb is read-only", { t: i18next.t });
72339
72360
  return true;
72340
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
+ }
72341
72385
  /***************************
72342
72386
  * The model
72343
72387
  ***************************/
@@ -72346,10 +72390,36 @@ function refuse_if_readonly(gobj, event) {
72346
72390
  * schema set of the yuno is here: everything below — the
72347
72391
  * drawing, the validation, the export — is computed from these
72348
72392
  * and asks the backend nothing more.
72393
+ *
72394
+ * THE LOADING SCREEN IS DRAWN, not assumed. ST_LOADING declares
72395
+ * none of the actions of a screen, so the screen it replaces goes
72396
+ * and the toolbar stops answering: left up, a click on a card, on
72397
+ * Back or on the drawing answered "Event NOT DEFINED in state
72398
+ * ST_LOADING" and was lost (third independent review). A dialog
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.
72404
+ *
72405
+ * `keep_written`: a load the host did not ask for -- the reconnect,
72406
+ * a write that turned out done -- keeps what this session wrote.
72407
+ * Only the host's EV_REFRESH (a Save sends it) forgets it.
72408
+ *
72409
+ * A load that cannot leave WHOLE did not happen (out of session it
72410
+ * is not even asked): the records the model was built on are put
72411
+ * back (emptied, the next write patched them into a model with no
72412
+ * treedb), the round is spent so a request that did leave is
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()).
72349
72416
  ***************************************************************/
72350
- function request_model(gobj) {
72417
+ function request_model(gobj, keep_written) {
72351
72418
  let priv = gobj.priv;
72352
72419
  if (!(0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno")) return -1;
72420
+ forget_import_plan(gobj);
72421
+ let previous = priv.records;
72422
+ priv.records_before = previous;
72353
72423
  priv.records = {
72354
72424
  treedbs: [],
72355
72425
  topics: [],
@@ -72358,22 +72428,32 @@ function request_model(gobj) {
72358
72428
  priv.load_error = "";
72359
72429
  priv.pending = 0;
72360
72430
  priv.load_round++;
72431
+ priv.load_keeps_written = !!keep_written;
72361
72432
  (0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_LOADING");
72362
- show_notice(gobj, "loading the schemas");
72363
- for (let topic_name of [
72364
- T_TREEDBS,
72365
- T_TOPICS,
72366
- T_COLS
72367
- ]) if (remote_command(gobj, "nodes", {
72368
- topic_name,
72369
- options: { list_dict: true }
72370
- }, { round: priv.load_round }) === 0) priv.pending++;
72371
- if (priv.pending === 0) {
72433
+ set_busy(gobj, false);
72434
+ render(gobj);
72435
+ let left = 0;
72436
+ if (transport_in_session(gobj)) {
72437
+ for (let topic_name of [
72438
+ T_TREEDBS,
72439
+ T_TOPICS,
72440
+ T_COLS
72441
+ ]) if (remote_command(gobj, "nodes", {
72442
+ topic_name,
72443
+ options: { list_dict: true }
72444
+ }, { round: priv.load_round }) === 0) left++;
72445
+ }
72446
+ if (left < 3) {
72447
+ priv.records = previous;
72448
+ priv.records_before = null;
72449
+ priv.load_round++;
72450
+ priv.reload_on_open = true;
72372
72451
  priv.load_error = (0, i18next.t)("cannot reach the treedb");
72373
- (0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_IDLE");
72374
- show_notice(gobj, "cannot reach the treedb");
72452
+ if (priv.model) yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", { t: i18next.t });
72453
+ end_load(gobj);
72375
72454
  return -1;
72376
72455
  }
72456
+ priv.pending = left;
72377
72457
  return 0;
72378
72458
  }
72379
72459
  /***************************************************************
@@ -72436,15 +72516,19 @@ function build_model(gobj) {
72436
72516
  * From here on, "did this session change it?" has an answer.
72437
72517
  *
72438
72518
  * Called ONLY when records arrive from the store: that is what a
72439
- * measurement of "changed since" can be measured against. Both
72440
- * halves reset together -- a baseline without its `written` is
72441
- * the same false positive from the other side, since every
72442
- * version equals a baseline just taken.
72519
+ * measurement of "changed since" can be measured against.
72520
+ *
72521
+ * `written` is forgotten only by a load the HOST asked for (its
72522
+ * EV_REFRESH, which a Save sends). Since M36 a write moves no
72523
+ * version, so `written` is the draft chip of what this session
72524
+ * wrote and no longer half of a version measurement: a reload of
72525
+ * the reconnect, or of a write that turned out done, forgot the
72526
+ * chips of writes the host had not been told about.
72443
72527
  ***************************************************************/
72444
72528
  function start_measuring(gobj) {
72445
72529
  let priv = gobj.priv;
72446
72530
  priv.baseline = {};
72447
- priv.written = {};
72531
+ if (!priv.load_keeps_written) priv.written = {};
72448
72532
  priv.host_ids = {};
72449
72533
  if (!priv.model) return;
72450
72534
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
@@ -72457,9 +72541,10 @@ function start_measuring(gobj) {
72457
72541
  * until the host saves it (C_TREEDB's save-schema, which raises the
72458
72542
  * versions of what changed). Two sources say a topic is one, and
72459
72543
  * they are kept APART because they are forgotten differently:
72460
- * - `written`: what THIS session wrote since the model was loaded
72461
- * (a reload, the host's EV_REFRESH, forgets it -- the store says
72462
- * 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());
72463
72548
  * - `host_ids`: what the host said last (EV_DRAFTS), replaced WHOLE
72464
72549
  * by the next EV_DRAFTS. Folded into `written`, a topic the host
72465
72550
  * named once stayed a draft after the Save that published it.
@@ -72626,8 +72711,12 @@ function render_toolbar(gobj) {
72626
72711
  let state = (0, _yuneta_gobj_js.gobj_current_state)(gobj);
72627
72712
  let readonly = is_readonly(gobj);
72628
72713
  let has_model = !!priv.model;
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);
72717
+ let in_flight = state === "ST_SAVING" || state === "ST_LOADING";
72629
72718
  let $left = [];
72630
- if (priv.treedb_id) $left.push([
72719
+ if (priv.treedb_id && on_a_treedb) $left.push([
72631
72720
  "button",
72632
72721
  {
72633
72722
  class: "SCHEMA_BACK button",
@@ -72700,17 +72789,17 @@ function render_toolbar(gobj) {
72700
72789
  $trail
72701
72790
  ]);
72702
72791
  let $right = [];
72703
- if (priv.treedb_id && has_model) {
72792
+ if (priv.treedb_id && has_model && treedb_here) {
72704
72793
  $right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-compass-drafting", priv.diagram ? "topics" : "diagram", "EV_TOGGLE_DIAGRAM", false));
72705
72794
  $right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-square-check", "check", "EV_VALIDATE", false));
72706
72795
  $right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-download", "export", "EV_EXPORT", false));
72707
72796
  if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-upload", "import", "EV_IMPORT", false));
72708
72797
  }
72709
- if (priv.topic_name && !readonly && !priv.diagram) {
72798
+ if (priv.topic_name && !readonly && !priv.diagram && topic_here) {
72710
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));
72711
72800
  $right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus", "new column", "EV_ADD_COLUMN", false));
72712
72801
  }
72713
- if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram) $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));
72714
72803
  $right.push(toolbar_button("SCHEMA_REFRESH_BTN", "yi-arrows-rotate", "refresh", "EV_REFRESH", state === "ST_LOADING"));
72715
72804
  let $bar = (0, _yuneta_gobj_js.createElement2)([
72716
72805
  "div",
@@ -72736,7 +72825,7 @@ function render_toolbar(gobj) {
72736
72825
  ]);
72737
72826
  for (let $button of $bar.querySelectorAll("button")) {
72738
72827
  $button.__gobj__ = gobj;
72739
- if (state === "ST_SAVING") $button.disabled = true;
72828
+ if (in_flight) $button.disabled = true;
72740
72829
  }
72741
72830
  $toolbar.appendChild($bar);
72742
72831
  (0, _yuneta_gobj_js.refresh_language)($toolbar, i18next.t);
@@ -72754,16 +72843,19 @@ function render(gobj) {
72754
72843
  render_toolbar(gobj);
72755
72844
  if (state === "ST_LOADING") {
72756
72845
  show_notice(gobj, "loading the schemas");
72846
+ destroy_diagram(gobj);
72757
72847
  clear($body);
72758
72848
  return;
72759
72849
  }
72760
72850
  if (state === "ST_IDLE") {
72761
72851
  show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected", priv.load_error);
72852
+ destroy_diagram(gobj);
72762
72853
  clear($body);
72763
72854
  return;
72764
72855
  }
72765
72856
  if (state === "ST_EMPTY") {
72766
72857
  show_notice(gobj, "this yuno stores no schema");
72858
+ destroy_diagram(gobj);
72767
72859
  clear($body);
72768
72860
  return;
72769
72861
  }
@@ -73452,12 +73544,16 @@ function render_diagram(gobj) {
73452
73544
  let priv = gobj.priv;
73453
73545
  let $body = $of(gobj, ".SCHEMA_BODY");
73454
73546
  let treedb = current_treedb(gobj);
73455
- if (!$body || !treedb) {
73547
+ if (!$body) return;
73548
+ if (!treedb) {
73549
+ destroy_diagram(gobj);
73550
+ clear($body);
73456
73551
  show_notice(gobj, "that treedb is not here any more", priv.treedb_id);
73457
73552
  return;
73458
73553
  }
73459
73554
  if (!(0, _yuneta_gobj_js.gclass_find_by_name)("C_YUI_TREEDB_SCHEMA")) {
73460
73555
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: C_YUI_TREEDB_SCHEMA not registered by the app`);
73556
+ clear($body);
73461
73557
  show_notice(gobj, "the schema drawing is not available");
73462
73558
  return;
73463
73559
  }
@@ -73523,6 +73619,7 @@ function close_dialog(gobj) {
73523
73619
  function open_dialog(gobj, $content, title, logical, title_prefix) {
73524
73620
  let priv = gobj.priv;
73525
73621
  close_dialog(gobj);
73622
+ priv.dialog_gen = priv.model_gen;
73526
73623
  priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
73527
73624
  dialog: true,
73528
73625
  title,
@@ -73569,11 +73666,28 @@ function name_control($spec, label) {
73569
73666
  $spec[1]["aria-label"] = (0, i18next.t)(label);
73570
73667
  $spec[1]["data-i18n-aria-label"] = label;
73571
73668
  }
73669
+ if (!$spec[1].title) {
73670
+ $spec[1].title = (0, i18next.t)(label);
73671
+ $spec[1]["data-i18n-title"] = label;
73672
+ }
73572
73673
  return true;
73573
73674
  }
73574
73675
  for (let child of $spec[2] || []) if (name_control(child, label)) return true;
73575
73676
  return false;
73576
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
+ }
73577
73691
  function field(logical, name, label, $control, help) {
73578
73692
  name_control($control, label);
73579
73693
  return [
@@ -73750,7 +73864,7 @@ function read_flags($root) {
73750
73864
  * than not offering it.
73751
73865
  ***************************************************************/
73752
73866
  function open_column_form(gobj, topic, col, prefill) {
73753
- gobj.priv;
73867
+ let priv = gobj.priv;
73754
73868
  let record = prefill || col && col.record || {};
73755
73869
  let creating = !col;
73756
73870
  let type = record.type || "string";
@@ -73760,6 +73874,7 @@ function open_column_form(gobj, topic, col, prefill) {
73760
73874
  let hook_col = hook_topic ? hook[hook_topic] : "";
73761
73875
  let treedb = current_treedb(gobj);
73762
73876
  let sibling_topics = treedb ? treedb.topics.map((x) => x.name) : [];
73877
+ let model_gen = priv.model_gen;
73763
73878
  let $form = (0, _yuneta_gobj_js.createElement2)([
73764
73879
  "div",
73765
73880
  { class: "SCHEMA_COL_FORM box" },
@@ -73822,10 +73937,10 @@ function open_column_form(gobj, topic, col, prefill) {
73822
73937
  },
73823
73938
  [[
73824
73939
  "button",
73825
- {
73940
+ name_attrs("cancel", {
73826
73941
  class: "SCHEMA_COL_FORM_CANCEL button",
73827
73942
  type: "button"
73828
- },
73943
+ }),
73829
73944
  [[
73830
73945
  "span",
73831
73946
  { i18n: "cancel" },
@@ -73833,10 +73948,10 @@ function open_column_form(gobj, topic, col, prefill) {
73833
73948
  ]]
73834
73949
  ], [
73835
73950
  "button",
73836
- {
73951
+ name_attrs("save", {
73837
73952
  class: "SCHEMA_COL_FORM_SAVE button is-primary",
73838
73953
  type: "button"
73839
- },
73954
+ }),
73840
73955
  [[
73841
73956
  "span",
73842
73957
  { class: "icon" },
@@ -73876,7 +73991,8 @@ function open_column_form(gobj, topic, col, prefill) {
73876
73991
  topic: topic.name,
73877
73992
  col: creating ? "" : col.name,
73878
73993
  creating,
73879
- values
73994
+ values,
73995
+ model_gen
73880
73996
  }, gobj);
73881
73997
  });
73882
73998
  open_dialog(gobj, $form, creating ? "new column" : "edit column", "SCHEMA_COL_FORM_DIALOG", creating ? topic.name : `${topic.name}.${col.name}`);
@@ -73899,6 +74015,7 @@ function open_topic_form(gobj, treedb, topic) {
73899
74015
  let record = topic && topic.record || {};
73900
74016
  let creating = !topic;
73901
74017
  let col_names = topic ? topic.cols.map((c) => c.name) : [];
74018
+ let model_gen = gobj.priv.model_gen;
73902
74019
  let $form = (0, _yuneta_gobj_js.createElement2)([
73903
74020
  "div",
73904
74021
  { class: "SCHEMA_TOPIC_FORM box" },
@@ -73957,10 +74074,10 @@ function open_topic_form(gobj, treedb, topic) {
73957
74074
  },
73958
74075
  [[
73959
74076
  "button",
73960
- {
74077
+ name_attrs("cancel", {
73961
74078
  class: "SCHEMA_TOPIC_FORM_CANCEL button",
73962
74079
  type: "button"
73963
- },
74080
+ }),
73964
74081
  [[
73965
74082
  "span",
73966
74083
  { i18n: "cancel" },
@@ -73968,10 +74085,10 @@ function open_topic_form(gobj, treedb, topic) {
73968
74085
  ]]
73969
74086
  ], [
73970
74087
  "button",
73971
- {
74088
+ name_attrs("save", {
73972
74089
  class: "SCHEMA_TOPIC_FORM_SAVE button is-primary",
73973
74090
  type: "button"
73974
- },
74091
+ }),
73975
74092
  [[
73976
74093
  "span",
73977
74094
  { class: "icon" },
@@ -73994,7 +74111,8 @@ function open_topic_form(gobj, treedb, topic) {
73994
74111
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SAVE_TOPIC", {
73995
74112
  topic: creating ? "" : topic.name,
73996
74113
  creating,
73997
- values: read_form($form)
74114
+ values: read_form($form),
74115
+ model_gen
73998
74116
  }, gobj);
73999
74117
  });
74000
74118
  open_dialog(gobj, $form, creating ? "new topic" : "edit topic", "SCHEMA_TOPIC_FORM_DIALOG", creating ? treedb.id : `${treedb.id}.${topic.name}`);
@@ -74274,6 +74392,7 @@ function copy_text(gobj, $text) {
74274
74392
  * plan itself, so what is confirmed is what runs.
74275
74393
  ***************************************************************/
74276
74394
  function open_import(gobj, treedb) {
74395
+ let model_gen = gobj.priv.model_gen;
74277
74396
  let $content = (0, _yuneta_gobj_js.createElement2)([
74278
74397
  "div",
74279
74398
  { class: "SCHEMA_IMPORT box" },
@@ -74288,12 +74407,12 @@ function open_import(gobj, treedb) {
74288
74407
  ],
74289
74408
  [
74290
74409
  "textarea",
74291
- {
74410
+ name_attrs("schema", {
74292
74411
  class: "SCHEMA_IMPORT_TEXT textarea is-small is-family-monospace",
74293
74412
  rows: "12",
74294
74413
  spellcheck: "false",
74295
74414
  placeholder: "{\"id\": \"...\", \"schema_version\": \"1\", \"topics\": []}"
74296
- },
74415
+ }),
74297
74416
  ""
74298
74417
  ],
74299
74418
  [
@@ -74326,10 +74445,10 @@ function open_import(gobj, treedb) {
74326
74445
  [
74327
74446
  [
74328
74447
  "button",
74329
- {
74448
+ name_attrs("cancel", {
74330
74449
  class: "SCHEMA_IMPORT_CANCEL button",
74331
74450
  type: "button"
74332
- },
74451
+ }),
74333
74452
  [[
74334
74453
  "span",
74335
74454
  { i18n: "cancel" },
@@ -74338,10 +74457,10 @@ function open_import(gobj, treedb) {
74338
74457
  ],
74339
74458
  [
74340
74459
  "button",
74341
- {
74460
+ name_attrs("preview", {
74342
74461
  class: "SCHEMA_IMPORT_PREVIEW button",
74343
74462
  type: "button"
74344
- },
74463
+ }),
74345
74464
  [[
74346
74465
  "span",
74347
74466
  { class: "icon" },
@@ -74354,11 +74473,11 @@ function open_import(gobj, treedb) {
74354
74473
  ],
74355
74474
  [
74356
74475
  "button",
74357
- {
74476
+ name_attrs("import", {
74358
74477
  class: "SCHEMA_IMPORT_RUN button is-warning",
74359
74478
  type: "button",
74360
74479
  disabled: "disabled"
74361
- },
74480
+ }),
74362
74481
  [[
74363
74482
  "span",
74364
74483
  { class: "icon" },
@@ -74385,18 +74504,31 @@ function open_import(gobj, treedb) {
74385
74504
  evt.stopPropagation();
74386
74505
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_PREVIEW_IMPORT", {
74387
74506
  text: $text.value,
74388
- prune: $prune.checked
74507
+ prune: $prune.checked,
74508
+ model_gen
74389
74509
  }, gobj);
74390
74510
  });
74391
74511
  $run.addEventListener("click", (evt) => {
74392
74512
  evt.stopPropagation();
74393
- (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);
74394
74514
  });
74395
74515
  gobj.priv.import_pane = $plan_pane;
74396
74516
  gobj.priv.import_run = $run;
74397
74517
  open_dialog(gobj, $content, "import", "SCHEMA_IMPORT_DIALOG", treedb.id);
74398
74518
  }
74399
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
+ /***************************************************************
74400
74532
  * The plan, drawn where the paste is: one line per write, the
74401
74533
  * deletions apart and counted, because they are what the
74402
74534
  * operator is being asked about.
@@ -74523,7 +74655,8 @@ function open_orphans(gobj) {
74523
74655
  { class: "SCHEMA_ORPHAN_ACTIONS has-text-right" },
74524
74656
  readonly ? [] : [row_icon(gobj, "SCHEMA_ORPHAN_DELETE", "yi-trash", "delete", "EV_DELETE_ORPHAN", {
74525
74657
  kind,
74526
- id: entry.id
74658
+ id: entry.id,
74659
+ model_gen: priv.model_gen
74527
74660
  })]
74528
74661
  ]
74529
74662
  ]
@@ -74695,6 +74828,10 @@ function transport_dropped(gobj) {
74695
74828
  priv.load_round++;
74696
74829
  priv.pending = 0;
74697
74830
  priv.load_error = "";
74831
+ if (priv.records_before) {
74832
+ priv.records = priv.records_before;
74833
+ priv.records_before = null;
74834
+ }
74698
74835
  return end_load(gobj);
74699
74836
  }
74700
74837
  return 0;
@@ -74705,11 +74842,22 @@ function transport_dropped(gobj) {
74705
74842
  * in flight (ac_show() kept it), or the one it had. Without a
74706
74843
  * model there is no screen to go to: the position is kept for the
74707
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.
74708
74851
  ***************************************************************/
74709
74852
  function end_load(gobj) {
74710
74853
  let priv = gobj.priv;
74711
74854
  let seg = priv.pending_seg;
74712
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
+ }
74713
74861
  if (priv.model) {
74714
74862
  if (seg !== null) return apply_seg(gobj, seg);
74715
74863
  return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
@@ -74727,26 +74875,21 @@ function end_load(gobj) {
74727
74875
  ***************************************************************/
74728
74876
  function end_writes(gobj, error) {
74729
74877
  let priv = gobj.priv;
74878
+ let seg = priv.pending_seg;
74730
74879
  priv.save_queue = [];
74880
+ priv.pending_seg = null;
74731
74881
  (0, _yuneta_gobj_js.gobj_change_state)(gobj, priv.save_return || "ST_TREEDBS");
74732
74882
  priv.save_return = "";
74733
74883
  show_notice(gobj, "");
74734
74884
  set_busy(gobj, false);
74735
- if (priv.pending_seg !== null) {
74736
- let seg = priv.pending_seg;
74737
- priv.pending_seg = null;
74738
- if (!error) {
74739
- apply_seg(gobj, seg);
74740
- if (priv.save_wrote) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
74741
- priv.save_wrote = false;
74742
- return reload_if_owed(gobj);
74743
- }
74744
- }
74745
74885
  if (error) yui_shell_show_error(yui_shell_of(gobj), error, { t: i18next.t });
74746
74886
  if (priv.save_wrote) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
74747
74887
  priv.save_wrote = false;
74748
- render(gobj);
74749
- publish_check(gobj);
74888
+ if (seg !== null) apply_seg(gobj, seg);
74889
+ else {
74890
+ render(gobj);
74891
+ publish_check(gobj);
74892
+ }
74750
74893
  reload_if_owed(gobj);
74751
74894
  return error ? -1 : 0;
74752
74895
  }
@@ -74763,7 +74906,7 @@ function reload_if_owed(gobj) {
74763
74906
  priv.reload_on_open = true;
74764
74907
  return 0;
74765
74908
  }
74766
- return request_model(gobj);
74909
+ return request_model(gobj, true);
74767
74910
  }
74768
74911
  /***************************************************************
74769
74912
  * The record a column form produces.
@@ -74829,6 +74972,7 @@ function topic_record(gobj, treedb, topic, values) {
74829
74972
  ***************************************************************/
74830
74973
  function go(gobj, treedb_id, topic_name, diagram, mirror) {
74831
74974
  let priv = gobj.priv;
74975
+ priv.pending_seg = null;
74832
74976
  priv.treedb_id = treedb_id || "";
74833
74977
  priv.topic_name = topic_name || "";
74834
74978
  priv.diagram = !!diagram;
@@ -74929,10 +75073,20 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74929
75073
  else if (priv.records && Array.isArray(priv.records[topic_name])) priv.records[topic_name] = Array.isArray(data) ? data : [];
74930
75074
  if (priv.pending > 0) return 0;
74931
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
+ }
74932
75084
  priv.model = null;
74933
75085
  return end_load(gobj);
74934
75086
  }
74935
75087
  priv.order_undo = {};
75088
+ priv.records_before = null;
75089
+ priv.model_gen++;
74936
75090
  build_model(gobj);
74937
75091
  start_measuring(gobj);
74938
75092
  return end_load(gobj);
@@ -74945,7 +75099,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74945
75099
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' of '${topic_name}' answered for a write that is over (result ${result}): ignored`);
74946
75100
  return 0;
74947
75101
  }
74948
- return late_write_done(gobj, command, topic_name);
75102
+ return late_write_done(gobj, command, topic_name, data);
74949
75103
  }
74950
75104
  if (result < 0) return end_writes(gobj, comment || (0, i18next.t)("the treedb refused the write"));
74951
75105
  priv.save_wrote = true;
@@ -74954,9 +75108,19 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74954
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;
74955
75109
  if (record) patch_record(gobj, topic_name, record);
74956
75110
  else {
74957
- (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 });
75114
+ if (topic_name === T_TOPICS || topic_name === T_COLS) {
75115
+ let write = priv.save_queue[0];
75116
+ mark_written(gobj, topic_name, write ? write.record : null);
75117
+ }
75118
+ priv.reload_after_write = false;
74958
75119
  priv.save_queue = [];
74959
- request_model(gobj);
75120
+ priv.save_return = "";
75121
+ priv.save_wrote = false;
75122
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
75123
+ request_model(gobj, true);
74960
75124
  return 0;
74961
75125
  }
74962
75126
  }
@@ -74983,11 +75147,18 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74983
75147
  * the view was answered once, as failed -- and echoes the node
74984
75148
  * event instead, which this view does not hear: there, Refresh is
74985
75149
  * what reads the store.
75150
+ *
75151
+ * It is a WRITE like any other for the host (EV_RECORD_WRITTEN):
75152
+ * the host was told nothing when it was given up, and it owns the
75153
+ * Save that publishes it. The reload keeps what this session wrote
75154
+ * (request_model()), and the record it answered marks its topic.
74986
75155
  ***************************************************************/
74987
- function late_write_done(gobj, command, topic_name) {
75156
+ function late_write_done(gobj, command, topic_name, data) {
74988
75157
  let priv = gobj.priv;
74989
75158
  let state = (0, _yuneta_gobj_js.gobj_current_state)(gobj);
74990
75159
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' of '${topic_name}' was given up, and it was DONE: the model is read again`);
75160
+ mark_written_record(gobj, topic_name, (0, _yuneta_gobj_js.is_object)(data) ? data : Array.isArray(data) && (0, _yuneta_gobj_js.is_object)(data[0]) ? data[0] : null);
75161
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
74991
75162
  if (state === "ST_LOADING") return 0;
74992
75163
  if (state === "ST_SAVING") {
74993
75164
  priv.reload_after_write = true;
@@ -74997,7 +75168,29 @@ function late_write_done(gobj, command, topic_name) {
74997
75168
  priv.reload_on_open = true;
74998
75169
  return 0;
74999
75170
  }
75000
- return request_model(gobj);
75171
+ return request_model(gobj, true);
75172
+ }
75173
+ /***************************************************************
75174
+ * The topic a record written belongs to, read off the RECORD and
75175
+ * not off the screen: a write answered late may be about a topic
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).
75184
+ ***************************************************************/
75185
+ function mark_written_record(gobj, topic_name, record) {
75186
+ let priv = gobj.priv;
75187
+ if (!record) return;
75188
+ if (topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
75189
+ priv.written[record.id] = true;
75190
+ return;
75191
+ }
75192
+ if (topic_name !== T_COLS) return;
75193
+ for (let ref of parse_fkey_ref(record.topics)) priv.written[ref.id] = true;
75001
75194
  }
75002
75195
  /***************************************************************
75003
75196
  * Remember which topic this write belongs to, which is what
@@ -75056,7 +75249,7 @@ function ac_transport_state(gobj, event, kw, src) {
75056
75249
  }
75057
75250
  if ((!priv.model || priv.reload_on_open) && state !== "ST_LOADING" && state !== "ST_SAVING") {
75058
75251
  priv.reload_on_open = false;
75059
- return request_model(gobj);
75252
+ return request_model(gobj, true);
75060
75253
  }
75061
75254
  render_toolbar(gobj);
75062
75255
  return 0;
@@ -75071,7 +75264,30 @@ function ac_language_changed$1(gobj, event, kw, src) {
75071
75264
  return 0;
75072
75265
  }
75073
75266
  function ac_refresh$1(gobj, event, kw, src) {
75074
- return request_model(gobj);
75267
+ return request_model(gobj, false);
75268
+ }
75269
+ /***************************************************************
75270
+ * What a DIALOG sends while the model is being read again: the
75271
+ * Save of a form left open when the reload started, a
75272
+ * confirmation, an import. The loading screen took the rest of
75273
+ * the view away (request_model()); a dialog is the one thing the
75274
+ * operator can still reach, and what it asks is computed against
75275
+ * a model the answers in the air are about to replace.
75276
+ *
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.
75286
+ ***************************************************************/
75287
+ function ac_wait_for_the_load(gobj, event, kw, src) {
75288
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, the schemas are loading`);
75289
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas are loading: wait for them", { t: i18next.t });
75290
+ return -1;
75075
75291
  }
75076
75292
  /***************************************************************
75077
75293
  * {drafts: {treedb_name: [topic names]}} -- the host says which
@@ -75136,6 +75352,7 @@ function ac_show_orphans(gobj, event, kw, src) {
75136
75352
  * to feed.
75137
75353
  ***************************************************************/
75138
75354
  function confirm_then(gobj, message, detail, kw) {
75355
+ kw = Object.assign({ model_gen: gobj.priv.model_gen }, kw);
75139
75356
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
75140
75357
  t: i18next.t,
75141
75358
  detail
@@ -75197,6 +75414,7 @@ function ac_save_column(gobj, event, kw, src) {
75197
75414
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
75198
75415
  let col = kw.creating ? null : find_col(priv.model, priv.treedb_id, kw.topic, kw.col);
75199
75416
  if (refuse_if_readonly(gobj, event)) return -1;
75417
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75200
75418
  if (!topic || !kw.creating && !col) {
75201
75419
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no column: ${kw.topic}.${kw.col}`);
75202
75420
  return -1;
@@ -75318,6 +75536,7 @@ function ac_save_topic(gobj, event, kw, src) {
75318
75536
  let treedb = current_treedb(gobj);
75319
75537
  let topic = kw.creating ? null : find_topic(priv.model, priv.treedb_id, kw.topic);
75320
75538
  if (refuse_if_readonly(gobj, event)) return -1;
75539
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75321
75540
  if (!treedb || !kw.creating && !topic) {
75322
75541
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no topic: ${kw.topic}`);
75323
75542
  return -1;
@@ -75360,6 +75579,7 @@ function ac_delete_topic(gobj, event, kw, src) {
75360
75579
  ***************************************************************/
75361
75580
  function ac_delete_orphan(gobj, event, kw, src) {
75362
75581
  if (refuse_if_readonly(gobj, event)) return -1;
75582
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75363
75583
  if ((0, _yuneta_gobj_js.empty_string)(kw.id)) {
75364
75584
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names nothing`);
75365
75585
  return -1;
@@ -75416,6 +75636,7 @@ function ac_preview_import(gobj, event, kw, src) {
75416
75636
  let priv = gobj.priv;
75417
75637
  let treedb = current_treedb(gobj);
75418
75638
  let incoming = null;
75639
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75419
75640
  if (!treedb) {
75420
75641
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no treedb open`);
75421
75642
  return -1;
@@ -75443,6 +75664,7 @@ function ac_preview_import(gobj, event, kw, src) {
75443
75664
  function ac_apply_import(gobj, event, kw, src) {
75444
75665
  let plan = gobj.priv.import_plan;
75445
75666
  if (refuse_if_readonly(gobj, event)) return -1;
75667
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75446
75668
  if (!plan || plan.writes.length === 0) {
75447
75669
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no plan to run`);
75448
75670
  return -1;
@@ -75485,6 +75707,7 @@ function run_import(gobj) {
75485
75707
  function ac_confirmed(gobj, event, kw, src) {
75486
75708
  let priv = gobj.priv;
75487
75709
  if (refuse_if_readonly(gobj, event)) return -1;
75710
+ if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75488
75711
  if (kw.what === "import") return run_import(gobj);
75489
75712
  if (kw.what === "topic") {
75490
75713
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
@@ -75619,7 +75842,38 @@ function create_gclass$1(gclass_name) {
75619
75842
  ];
75620
75843
  const states = [
75621
75844
  ["ST_IDLE", COMMON.slice()],
75622
- ["ST_LOADING", COMMON.slice()],
75845
+ ["ST_LOADING", COMMON.concat([
75846
+ [
75847
+ "EV_SAVE_COLUMN",
75848
+ ac_wait_for_the_load,
75849
+ null
75850
+ ],
75851
+ [
75852
+ "EV_SAVE_TOPIC",
75853
+ ac_wait_for_the_load,
75854
+ null
75855
+ ],
75856
+ [
75857
+ "EV_DELETE_ORPHAN",
75858
+ ac_wait_for_the_load,
75859
+ null
75860
+ ],
75861
+ [
75862
+ "EV_PREVIEW_IMPORT",
75863
+ ac_wait_for_the_load,
75864
+ null
75865
+ ],
75866
+ [
75867
+ "EV_APPLY_IMPORT",
75868
+ ac_wait_for_the_load,
75869
+ null
75870
+ ],
75871
+ [
75872
+ "EV_CONFIRMED",
75873
+ ac_wait_for_the_load,
75874
+ null
75875
+ ]
75876
+ ])],
75623
75877
  ["ST_EMPTY", COMMON.slice()],
75624
75878
  ["ST_TREEDBS", COMMON.concat([
75625
75879
  [