@yuneta/gobj-ui 7.25.14 → 7.25.15

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
@@ -1790,6 +1790,25 @@ size. A card on its tier's default now saves its position and nothing else;
1790
1790
  a closed node saves no size at all (a square is not the card's size), its
1791
1791
  entry keeps what it had.
1792
1792
 
1793
+ **A Save that does not land is written again** (since `7.25.15`). `Save`
1794
+ writes one `__graphs__` record per topic that changed, and nothing answers such
1795
+ a write when it lands, so the engine takes it for granted when it leaves. A
1796
+ refused one was recorded as saved all the same: the next Save found nothing to
1797
+ write, and the arrangement was never stored. Now the host (`C_YUI_TREEDB_GRAPH`)
1798
+ tells the engine every way the write can fail -- the backend answers an error
1799
+ (shown as before), the transport refuses the command, or there is no session
1800
+ (the write is not sent) -- with `EV_GRAPHS_WRITE_REFUSED {topic}`. The engine
1801
+ forgets what it believed the backend holds for that topic
1802
+ (`forget_refused_graphs_write()` in `graph_save_plan.js`), logs a warning and
1803
+ lights Save again, so the next Save writes the topic. A host of its own that
1804
+ hosts `C_G6_NODES_TREE` sends the same event when its write of `__graphs__`
1805
+ fails:
1806
+
1807
+ ```js
1808
+ // in the host, when the answer to its update-node of __graphs__ is an error
1809
+ gobj_send_event(engine, "EV_GRAPHS_WRITE_REFUSED", {topic: record.topic}, gobj);
1810
+ ```
1811
+
1793
1812
  **Every saved look has a way back, in the context menu** (since `7.23.134`,
1794
1813
  replacing `reset sizes` / `reset topic sizes`). In edition, the node, the port
1795
1814
  and the edge menus each offer three resets — this one, its kind (`reset topic
@@ -2245,6 +2264,28 @@ else.
2245
2264
  gobj_send_event(editor, "EV_REFRESH", {}, host); // Save -> "wait for them"
2246
2265
  // ... the load lands (model_gen N+1): the form closes, the operator reopens it
2247
2266
  ```
2267
+ - **A move sent by the host closes the dialog of the screen it left**
2268
+ (7.25.15). `C_YUI_SHELL` keeps the overlays open on a move that changes only
2269
+ the subpath (a url typed in, a link), so a dialog outlived its screen: its
2270
+ Save, its import Preview or an orphan delete sent an event only that screen
2271
+ declares, answered *"Event NOT DEFINED"*, and the edit was lost with no word.
2272
+ Now an `EV_SHOW` that moves the view to another position -- at once, or when
2273
+ a load or a write in flight ends -- closes the column form, the topic form,
2274
+ the import (its plan goes with it) and the orphans, and tells the operator
2275
+ `the view moved: open the dialog again` (an i18n key the consumer's locales
2276
+ carry). The export and the check stay up: they show the texts they were
2277
+ opened with and send nothing a screen declares. The same position sent again
2278
+ closes nothing. A **confirmation** is a shell modal, which the move does not
2279
+ close; it carries the position it was asked on (`seg`), and a Yes answered
2280
+ on another position is refused with the same words. Before, a Yes to "delete
2281
+ this column?" asked on `db/users` and answered after a move to `db2/users`
2282
+ deleted `db2`'s column of the same name.
2283
+
2284
+ ```js
2285
+ gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host); // on db/users
2286
+ gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host); // form closed, said
2287
+ gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host); // same position: nothing
2288
+ ```
2248
2289
  - **A load refused IN session keeps the model it was replacing** (7.25.9): a
2249
2290
  routing adapter's deadline on a slow `nodes` answers -1 while the session
2250
2291
  is up. The records are put back, the screen and any open dialog stay, and
@@ -3038,9 +3038,8 @@ function maybe_apply_translator($node, opts) {
3038
3038
  * it -- until every handle is closed or has timed out. Handed the
3039
3039
  * first caller's handle and timer, a repeat asking `timeout: 0`
3040
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.
3041
+ * caller's handle closed a toast another was still showing.
3042
+ * The operator's ✕ closes it for everybody.
3044
3043
  ***************************************************************/
3045
3044
  function show_notification(shell, kind, message, opts) {
3046
3045
  let $layer = notification_layer(shell);
@@ -7425,7 +7424,7 @@ function json_diff_rows(doc_a, doc_b) {
7425
7424
  * A Date -> the value of a `datetime-local` input, in local time,
7426
7425
  * WITH seconds: the input is built with `step: 1`, which is what
7427
7426
  * lets it hold them. Written as "YYYY-MM-DDTHH:mm" it lost them on
7428
- * every save of any field (M26 of the 2026-09-21 review).
7427
+ * every save of any field.
7429
7428
  ************************************************************/
7430
7429
  function date_to_datetime_local(value) {
7431
7430
  if (!(value instanceof Date) || isNaN(value.getTime())) return "";
@@ -23130,8 +23129,7 @@ function yui_shell_show_route_map(shell, opts) {
23130
23129
  * answers, so the host has to answer EVERY way a write ends --
23131
23130
  * and the transport closing under a write in flight, or a
23132
23131
  * command that never left because there was no session, are two
23133
- * of them that nobody answered: the form stayed busy for ever
23134
- * (N8 of the 2026-09-22 review).
23132
+ * of them that nobody answered: the form stayed busy for ever.
23135
23133
  *
23136
23134
  * A write is known by its TOPIC and its `form_write` serial,
23137
23135
  * never by the serial alone: each topic has its own form, and
@@ -25682,8 +25680,8 @@ function ac_update_record(gobj, event, kw, src) {
25682
25680
  /************************************************************
25683
25681
  * A write sent with no session never leaves: C_IEVENT_CLI logs
25684
25682
  * "Not in session" and answers null, as it does when the command
25685
- * went -- so a form waiting for the answer waited for ever (N8 of
25686
- * the 2026-09-22 review). Refused before it is sent, aloud.
25683
+ * went -- so a form waiting for the answer waited for ever. Refused
25684
+ * before it is sent, aloud.
25687
25685
  ************************************************************/
25688
25686
  function refuse_without_session(gobj, topic_name) {
25689
25687
  if (is_connected$1(gobj)) return false;
@@ -26795,9 +26793,8 @@ function delete_impact(desc, records) {
26795
26793
  * that back. So a second busy(true) while already busy must not
26796
26794
  * run at all -- it read the buttons it had just disabled as
26797
26795
  * "disabled on their own", and the busy(false) of a refused write
26798
- * left Save and Cancel dead (N9 of the 2026-09-22 review: a record
26799
- * with a picked file went busy for the read, then busy again for
26800
- * the write). The caller owns `state` ({busy}) for the form's life.
26796
+ * left Save and Cancel dead (a record with a picked file went
26797
+ * busy for the read, then busy again for the write). The caller owns `state` ({busy}) for the form's life.
26801
26798
  *
26802
26799
  * Copyright (c) 2026, ArtGins.
26803
26800
  * All Rights Reserved.
@@ -26835,7 +26832,7 @@ function set_toolbar_busy($container, state, busy) {
26835
26832
  * (only its own `plaintext` formatter escapes), so a record's text
26836
26833
  * returned as a string is parsed as markup: `a<b and c>d` showed
26837
26834
  * as `ad`, and a field holding `<img src=x onerror=...>` ran it in
26838
- * the operator's browser (M31 of the 2026-09-21 review). Record
26835
+ * the operator's browser. Record
26839
26836
  * data goes in as a TEXT NODE, and a cell built around it as DOM,
26840
26837
  * never as an HTML string with the data interpolated.
26841
26838
  *
@@ -29597,7 +29594,7 @@ function ac_new_row(gobj, event, kw, src) {
29597
29594
  * What crosses the confirmation is the rows' IDENTITY, never their
29598
29595
  * position: the view applies EV_TREEDB_NODE_* of every writer while the
29599
29596
  * dialog is open, and a position names whatever row sits there when the
29600
- * person answers -- with force:true (A6 of the 2026-09-21 review).
29597
+ * person answers -- with force:true.
29601
29598
  ************************************************************/
29602
29599
  function ac_delete_rows(gobj, event, kw, src) {
29603
29600
  if (refuse_if_readonly$2(gobj, event)) return -1;
@@ -29808,8 +29805,8 @@ function ac_files_read(gobj, event, kw, src) {
29808
29805
  * open and BUSY, and the serial it waits for is taken here.
29809
29806
  *
29810
29807
  * A refused write used to close the form all the same and throw
29811
- * away what was typed, although the README said it stayed open
29812
- * (M25 of the 2026-09-21 review): the close was posted with the
29808
+ * away what was typed, although the README said it stayed open:
29809
+ * the close was posted with the
29813
29810
  * publish, before the backend had answered anything.
29814
29811
  ************************************************************/
29815
29812
  function wait_for_write_answer(gobj) {
@@ -30981,6 +30978,28 @@ function apply_graphs_echo(live, saved, records, rec) {
30981
30978
  delete saved[rec.topic];
30982
30979
  }
30983
30980
  }
30981
+ /***************************************************************
30982
+ * A write of one topic's arrangement did NOT land: the backend
30983
+ * refused it, the transport refused it on the way out, or there
30984
+ * was no session to send it on.
30985
+ *
30986
+ * saved {topic_name: properties} what the backend has
30987
+ * topic_name the topic of the refused record
30988
+ *
30989
+ * The view records a write as saved BEFORE it leaves, because
30990
+ * nothing answers it when it lands. So a refused one left the view
30991
+ * believing the backend held an arrangement it does not, and the
30992
+ * next Save found nothing to write. What the backend holds for the
30993
+ * topic is not known any more, so it is forgotten: the next Save
30994
+ * writes the topic again (plan_graph_saves()).
30995
+ *
30996
+ * Returns TRUE if there was something to forget.
30997
+ ***************************************************************/
30998
+ function forget_refused_graphs_write(saved, topic_name) {
30999
+ if (!(0, _yuneta_gobj_js.is_object)(saved) || !topic_name || !Object.prototype.hasOwnProperty.call(saved, topic_name)) return false;
31000
+ delete saved[topic_name];
31001
+ return true;
31002
+ }
30984
31003
  //#endregion
30985
31004
  //#region src/treedb_node_label.js
30986
31005
  /***********************************************************************
@@ -65788,6 +65807,28 @@ function ac_save_graph(gobj, event, kw, src) {
65788
65807
  return 0;
65789
65808
  }
65790
65809
  /************************************************************
65810
+ * The host says a write of `__graphs__` did not land: the
65811
+ * backend refused it, the transport refused it on the way out,
65812
+ * or there was no session to send it on. kw: {topic}
65813
+ *
65814
+ * save_topic_graph_properties() recorded it as saved before it
65815
+ * left. Forgotten now, and the Save is lit again: the next Save
65816
+ * writes the topic again. Before, the view believed the backend
65817
+ * held the arrangement, and the next Save had nothing to write.
65818
+ ************************************************************/
65819
+ function ac_graphs_write_refused(gobj, event, kw, src) {
65820
+ let priv = gobj.priv;
65821
+ let topic_name = kw && kw.topic;
65822
+ if (!topic_name) {
65823
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no topic`);
65824
+ return -1;
65825
+ }
65826
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the arrangement of '${topic_name}' was not saved: the next Save writes it again`);
65827
+ forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
65828
+ if (priv.edit_mode) mark_graph_dirty(gobj);
65829
+ return 0;
65830
+ }
65831
+ /************************************************************
65791
65832
  * Node created, from subscription
65792
65833
  ************************************************************/
65793
65834
  function ac_node_created(gobj, event, kw, src) {
@@ -66855,6 +66896,11 @@ function create_gclass$4(gclass_name) {
66855
66896
  ac_node_deleted,
66856
66897
  null
66857
66898
  ],
66899
+ [
66900
+ "EV_GRAPHS_WRITE_REFUSED",
66901
+ ac_graphs_write_refused,
66902
+ null
66903
+ ],
66858
66904
  [
66859
66905
  "EV_NODE_CLICK",
66860
66906
  ac_node_click$2,
@@ -67108,6 +67154,7 @@ function create_gclass$4(gclass_name) {
67108
67154
  ["EV_NODE_CREATED", 0],
67109
67155
  ["EV_NODE_UPDATED", 0],
67110
67156
  ["EV_NODE_DELETED", 0],
67157
+ ["EV_GRAPHS_WRITE_REFUSED", 0],
67111
67158
  ["EV_NODE_CLICK", 0],
67112
67159
  ["EV_EDGE_CLICK", 0],
67113
67160
  ["EV_NODE_CONTEXT_MENU", 0],
@@ -68284,7 +68331,7 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68284
68331
  let priv = gobj.priv;
68285
68332
  if (!priv.gobj_remote_yuno) {
68286
68333
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: No gobj_remote_yuno defined`);
68287
- return;
68334
+ return -1;
68288
68335
  }
68289
68336
  let command = "update-node";
68290
68337
  let kw = {
@@ -68296,7 +68343,11 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68296
68343
  };
68297
68344
  kw.__md_command__ = { topic_name };
68298
68345
  let ret = (0, _yuneta_gobj_js.gobj_command)(priv.gobj_remote_yuno, command, kw, gobj);
68299
- if (ret) (0, _yuneta_gobj_js.log_error)(ret);
68346
+ if (ret) {
68347
+ (0, _yuneta_gobj_js.log_error)(ret);
68348
+ return -1;
68349
+ }
68350
+ return 0;
68300
68351
  }
68301
68352
  /************************************************************
68302
68353
  * Command to remote service
@@ -68569,6 +68620,7 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68569
68620
  case "delete-node":
68570
68621
  case "link-nodes":
68571
68622
  case "unlink-nodes":
68623
+ if (result < 0 && command === "update-node") graphs_write_refused(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0), (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0));
68572
68624
  if (result >= 0 && (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", {
68573
68625
  treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
68574
68626
  topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
@@ -68812,12 +68864,40 @@ function ac_create_node(gobj, event, kw, src) {
68812
68864
  * Send to backend
68813
68865
  ********************************************/
68814
68866
  function ac_update_node(gobj, event, kw, src) {
68815
- let priv = gobj.priv;
68816
- if (refuse_if_readonly$1(gobj, event)) return -1;
68817
- let treedb_name = priv.treedb_name;
68867
+ let treedb_name = gobj.priv.treedb_name;
68818
68868
  let topic_name = kw.topic_name;
68819
68869
  let record = kw.record;
68820
- return treedb_update_node(gobj, treedb_name, topic_name, record, kw.options || {});
68870
+ let options = kw.options || {};
68871
+ if (refuse_if_readonly$1(gobj, event)) {
68872
+ graphs_write_refused(gobj, topic_name, record);
68873
+ return -1;
68874
+ }
68875
+ if (topic_name === "__graphs__" && !is_connected(gobj)) {
68876
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no session, the arrangement was not sent`);
68877
+ graphs_write_refused(gobj, topic_name, record);
68878
+ return -1;
68879
+ }
68880
+ let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
68881
+ if (ret < 0) graphs_write_refused(gobj, topic_name, record);
68882
+ return ret;
68883
+ }
68884
+ /************************************************************
68885
+ * A write of `__graphs__` did not land. The engine
68886
+ * (C_G6_NODES_TREE) took it for granted when it left -- nothing
68887
+ * answers it when it lands -- so it is told, and its next Save
68888
+ * writes the topic again. Other topics are the operator's data:
68889
+ * the answer's error says what happened to them.
68890
+ ************************************************************/
68891
+ function graphs_write_refused(gobj, topic_name, record) {
68892
+ let priv = gobj.priv;
68893
+ if (topic_name !== "__graphs__") return;
68894
+ let topic = record && typeof record === "object" ? record.topic || record.id : "";
68895
+ if (!topic) {
68896
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: a refused __graphs__ write names no topic`);
68897
+ return;
68898
+ }
68899
+ if (!priv.gobj_nodes_tree) return;
68900
+ (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", { topic }, gobj);
68821
68901
  }
68822
68902
  /********************************************
68823
68903
  * Message from G6_nodes_tree
@@ -70473,9 +70553,8 @@ function register_c_yui_treedb_schema() {
70473
70553
  * it, and that mark lived in the memory of the session only:
70474
70554
  * a reload of the page, a reconnect or a refresh of the model
70475
70555
  * lost the chip, the banner and the export warning while
70476
- * __system__ still differed from the file in use (N13 of the
70477
- * 2026-09-22 review). The host reads that difference from
70478
- * C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
70556
+ * __system__ still differed from the file in use. The host
70557
+ * reads that difference from C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
70479
70558
  * hands it here; the editor applies it to its model, in place
70480
70559
  * of what the host said before.
70481
70560
  *
@@ -72143,11 +72222,10 @@ function write_options(op) {
72143
72222
  * AN EDIT IS A DRAFT. A write here moves no version and reaches no
72144
72223
  * treedb: the host SAVES it (C_TREEDB's save-schema raises the
72145
72224
  * topic_version of what changed and the schema_version, once) and
72146
- * APPLIES it (apply-schema + a restart of the owning yuno). Until
72147
- * the owner's design of M36 (2026-09-21 review) every write here
72148
- * raised both versions, so an edit half made was already the
72149
- * schema of the next start. The topic list marks what this session
72150
- * wrote and has not saved (EV_REFRESH forgets it: the view's own
72225
+ * APPLIES it (apply-schema + a restart of the owning yuno). If a
72226
+ * write here raised both versions, an edit half made would already
72227
+ * be the schema of the next start. The topic list marks what this
72228
+ * session wrote and has not saved (EV_REFRESH forgets it: the view's own
72151
72229
  * Refresh button sends it, and a host may after a save -- the
72152
72230
  * agent console's does not) and what the host says is not saved
72153
72231
  * (EV_DRAFTS, replaced whole each time it is sent).
@@ -72213,6 +72291,7 @@ var HOOK_TOPICS = "topics";
72213
72291
  var HOOK_COLS = "cols";
72214
72292
  var DIAGRAM_SEG = "diagram";
72215
72293
  var PURPOSE = "schema_editor";
72294
+ var DIALOGS_THAT_READ_NOTHING = ["SCHEMA_EXPORT_DIALOG", "SCHEMA_REPORT_DIALOG"];
72216
72295
  /***************************************************************
72217
72296
  * Attrs
72218
72297
  ***************************************************************/
@@ -72245,6 +72324,7 @@ var PRIVATE_DATA$1 = {
72245
72324
  diagram_gobj: null,
72246
72325
  dialog: null,
72247
72326
  dialog_gen: 0,
72327
+ dialog_kind: "",
72248
72328
  save_queue: null,
72249
72329
  save_return: "",
72250
72330
  save_wrote: false,
@@ -72413,9 +72493,9 @@ function refuse_if_readonly(gobj, event) {
72413
72493
  * dumped by the machine trace).
72414
72494
  *
72415
72495
  * Refused, and said. A column form sends every field it shows, so
72416
- * its Save after a reload wrote the OLD record over the newer one
72417
- * (fourth independent review, probe A3); a form whose column the
72418
- * reload removed answered "names no column" for ever. end_load()
72496
+ * its Save after a reload wrote the OLD record over the newer one;
72497
+ * a form whose column the reload removed answered "names no column"
72498
+ * for ever. end_load()
72419
72499
  * closes such a dialog when the load lands; this is the same rule
72420
72500
  * for whatever arrives anyway -- a stale screen, a confirmation
72421
72501
  * that was up, a keyboard.
@@ -72435,8 +72515,7 @@ function built_on_a_replaced_model(gobj, event, kw) {
72435
72515
  * the model shown is kept, and it may be older than the store.
72436
72516
  * The session is up, so no reconnect will ask for it again -- it
72437
72517
  * was owed for as long as the session lasted, and a form opened
72438
- * on that model wrote every field of it over the newer record
72439
- * (fifth independent review; A3 of the fourth, by another path).
72518
+ * on that model wrote every field of it over the newer record.
72440
72519
  *
72441
72520
  * So the operator's NEXT action asks it. An edit -- a form, a
72442
72521
  * write, a confirmation, what reads the model whole (check,
@@ -72486,7 +72565,7 @@ function reload_after_move(gobj) {
72486
72565
  * none of the actions of a screen, so the screen it replaces goes
72487
72566
  * and the toolbar stops answering: left up, a click on a card, on
72488
72567
  * Back or on the drawing answered "Event NOT DEFINED in state
72489
- * ST_LOADING" and was lost (third independent review). A dialog
72568
+ * ST_LOADING" and was lost. A dialog
72490
72569
  * left open is answered in ST_LOADING instead (ac_wait_for_the_load),
72491
72570
  * and a load that LANDS closes it (end_load()): it was built on the
72492
72571
  * model the load replaced, and its Save sends every field it shows.
@@ -73706,6 +73785,7 @@ function close_dialog(gobj) {
73706
73785
  if (!priv.dialog) return;
73707
73786
  let dialog = priv.dialog;
73708
73787
  priv.dialog = null;
73788
+ priv.dialog_kind = "";
73709
73789
  priv.export_view = null;
73710
73790
  try {
73711
73791
  dialog.close();
@@ -73717,6 +73797,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
73717
73797
  let priv = gobj.priv;
73718
73798
  close_dialog(gobj);
73719
73799
  priv.dialog_gen = priv.model_gen;
73800
+ priv.dialog_kind = logical;
73720
73801
  priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
73721
73802
  dialog: true,
73722
73803
  title,
@@ -73725,6 +73806,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
73725
73806
  t: i18next.t,
73726
73807
  on_close: () => {
73727
73808
  priv.dialog = null;
73809
+ priv.dialog_kind = "";
73728
73810
  priv.export_view = null;
73729
73811
  }
73730
73812
  });
@@ -74919,11 +75001,11 @@ function transport_in_session(gobj) {
74919
75001
  * The state is part of the question, and that is only true
74920
75002
  * because NOTHING moves the editor out of ST_LOADING or ST_SAVING
74921
75003
  * but the end of what is in flight: a position the host sends
74922
- * meanwhile waits in `pending_seg` (ac_show()). 7.25.6 let EV_SHOW
74923
- * move a RELOAD out of ST_LOADING, and every answer of it was then
74924
- * "for a load that is over": the records it had emptied stayed
74925
- * empty, and the next write patched them into a model with no
74926
- * treedb at all.
75004
+ * meanwhile waits in `pending_seg` (ac_show()). If EV_SHOW moved a
75005
+ * RELOAD out of ST_LOADING, every answer of it would be "for a load
75006
+ * that is over": the records it had emptied would stay empty, and
75007
+ * the next write would patch them into a model with no treedb at
75008
+ * all.
74927
75009
  ***************************************************************/
74928
75010
  function is_current_load(gobj, md) {
74929
75011
  return (0, _yuneta_gobj_js.gobj_current_state)(gobj) === "ST_LOADING" && (0, _yuneta_gobj_js.kw_get_int)(gobj, md, "round", 0, 0) === gobj.priv.load_round;
@@ -75129,10 +75211,59 @@ function position_seg(gobj) {
75129
75211
  * ...and the same in reverse. A tail naming a treedb or a topic
75130
75212
  * the store does not have lands one level up rather than on an
75131
75213
  * empty screen: a shared link outlives the schema it points at.
75214
+ *
75215
+ * Every position the HOST sends comes through here, at once
75216
+ * (ac_show()) or when what was in flight ends (end_load(),
75217
+ * end_writes()). A move to another position closes the dialog of
75218
+ * the screen the editor left (close_dialog_left_behind()).
75132
75219
  ***************************************************************/
75133
75220
  function apply_seg(gobj, seg) {
75221
+ let before = position_seg(gobj);
75134
75222
  let pos = seg_position(seg);
75135
- return go(gobj, pos.treedb_id, pos.topic_name, pos.diagram, false);
75223
+ let ret = go(gobj, pos.treedb_id, pos.topic_name, pos.diagram, false);
75224
+ if (position_seg(gobj) !== before) close_dialog_left_behind(gobj);
75225
+ return ret;
75226
+ }
75227
+ /***************************************************************
75228
+ * The host moved the editor while a dialog was up. C_YUI_SHELL
75229
+ * keeps the overlays open on a move that changes only the subpath
75230
+ * (a url typed in, a link), so the dialog outlives its screen.
75231
+ *
75232
+ * A form, the import and the orphans send events that only the
75233
+ * screen they were opened from declares, and they name their
75234
+ * topic under the treedb open at the time. Left up on another
75235
+ * screen, their Save answered "Event NOT DEFINED" and the edit was
75236
+ * lost with no word. So the dialog is closed and the operator told.
75237
+ * The import plan goes with its dialog: it was computed for the
75238
+ * treedb the editor left.
75239
+ *
75240
+ * The export and the check stay: they show the texts they were
75241
+ * opened with, and what they send is legal in every state.
75242
+ ***************************************************************/
75243
+ function close_dialog_left_behind(gobj) {
75244
+ let priv = gobj.priv;
75245
+ if (!priv.dialog || DIALOGS_THAT_READ_NOTHING.includes(priv.dialog_kind)) return;
75246
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the host moved the view while the dialog ${priv.dialog_kind} was open: closed`);
75247
+ close_dialog(gobj);
75248
+ forget_import_plan(gobj);
75249
+ yui_shell_show_error(yui_shell_of(gobj), "the view moved: open the dialog again", { t: i18next.t });
75250
+ }
75251
+ /***************************************************************
75252
+ * A confirmation answered on another screen than the one it was
75253
+ * asked from. A confirmation is a shell modal, so a move sent by
75254
+ * the host does not close it. It names its topic and column, and
75255
+ * the actions look them up under the treedb open NOW: a Yes after
75256
+ * a move to another treedb deleted the topic or column of the same
75257
+ * name THERE.
75258
+ *
75259
+ * Refused, and said. A kw with no `seg` stamp is not a
75260
+ * confirmation's and is not asked.
75261
+ ***************************************************************/
75262
+ function asked_on_another_screen(gobj, event, kw) {
75263
+ if (!kw || kw.seg === void 0 || kw.seg === position_seg(gobj)) return false;
75264
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, it was asked on '${kw.seg}' and the view is on '${position_seg(gobj)}'`);
75265
+ yui_shell_show_error(yui_shell_of(gobj), "the view moved: open the dialog again", { t: i18next.t });
75266
+ return true;
75136
75267
  }
75137
75268
  function seg_position(seg) {
75138
75269
  let parts = String(seg || "").split("/").filter((x) => x.length > 0);
@@ -75308,8 +75439,7 @@ function late_write_done(gobj, command, topic_name, data) {
75308
75439
  * The fkey is read with parse_fkey_ref(), because the store answers
75309
75440
  * in list_dict -- `[{topic_name, id, hook_name}]` -- and not in the
75310
75441
  * `topics^<topic id>^cols` strings this view writes. Split as a
75311
- * string, every production answer marked nothing (fourth independent
75312
- * review, probe C).
75442
+ * string, every production answer marked nothing.
75313
75443
  ***************************************************************/
75314
75444
  function mark_written_record(gobj, topic_name, record) {
75315
75445
  let priv = gobj.priv;
@@ -75366,7 +75496,7 @@ function ac_hide(gobj, event, kw, src) {
75366
75496
  *
75367
75497
  * Nothing else ends a load or a write the drop cut: their answers
75368
75498
  * will never come, and waiting in ST_LOADING skipped the reload
75369
- * itself (M-1 of the independent review of 7.25.4).
75499
+ * itself.
75370
75500
  ***************************************************************/
75371
75501
  function ac_transport_state(gobj, event, kw, src) {
75372
75502
  let priv = gobj.priv;
@@ -75405,12 +75535,11 @@ function ac_refresh$1(gobj, event, kw, src) {
75405
75535
  * a model the answers in the air are about to replace.
75406
75536
  *
75407
75537
  * Refused, and SAID. Undeclared, it answered "Event NOT DEFINED
75408
- * in state ST_LOADING" and the edit was gone with no word (third
75409
- * independent review).
75538
+ * in state ST_LOADING" and the edit was gone with no word.
75410
75539
  *
75411
75540
  * And NOT "try again": the same Save, once the load was in, sent
75412
75541
  * the form built on the old model -- every field of the old record
75413
- * over the newer one (fourth independent review). The load that
75542
+ * over the newer one. The load that
75414
75543
  * lands closes such a dialog (end_load()); one that fails leaves it
75415
75544
  * up, on the model it was built on.
75416
75545
  ***************************************************************/
@@ -75424,8 +75553,7 @@ function ac_wait_for_the_load(gobj, event, kw, src) {
75424
75553
  * reload landed on zero treedbs while it was up) or ST_IDLE (no
75425
75554
  * model). A confirmation is a shell modal, not this view's dialog,
75426
75555
  * so the load that lands does not close it, and its Yes arrives
75427
- * anyway. Undeclared, it answered "Event NOT DEFINED" (fifth
75428
- * independent review).
75556
+ * anyway. Undeclared, it answered "Event NOT DEFINED".
75429
75557
  *
75430
75558
  * Refused with the stamp's own words: whatever it was decided on
75431
75559
  * is not on screen any more.
@@ -75441,13 +75569,12 @@ function ac_confirmed_on_nothing(gobj, event, kw, src) {
75441
75569
  * topics hold a draft in __system__ that is not saved (C_TREEDB's
75442
75570
  * saved-schema, `draft_changed`). The mark of a write lived in this
75443
75571
  * session's memory only, so a reload of the page, a reconnect or a
75444
- * refresh showed no draft while __system__ still differed (N13 of
75445
- * the 2026-09-22 review).
75572
+ * refresh showed no draft while __system__ still differed.
75446
75573
  *
75447
75574
  * REPLACES what the host said before -- the host is the truth after
75448
75575
  * a reload, and a Save is exactly the host saying "none any more".
75449
75576
  * It only ever added, and a topic stayed a draft after the Save that
75450
- * published it (M1 of the 2026-09-23 review). What this session wrote
75577
+ * published it. What this session wrote
75451
75578
  * stays marked: it is kept apart (see topic_is_draft()).
75452
75579
  ***************************************************************/
75453
75580
  function ac_drafts(gobj, event, kw, src) {
@@ -75504,7 +75631,10 @@ function ac_show_orphans(gobj, event, kw, src) {
75504
75631
  * to feed.
75505
75632
  ***************************************************************/
75506
75633
  function confirm_then(gobj, message, detail, kw) {
75507
- kw = Object.assign({ model_gen: gobj.priv.model_gen }, kw);
75634
+ kw = Object.assign({
75635
+ model_gen: gobj.priv.model_gen,
75636
+ seg: position_seg(gobj)
75637
+ }, kw);
75508
75638
  yui_shell_confirm_danger(yui_shell_of(gobj), message, {
75509
75639
  t: i18next.t,
75510
75640
  detail,
@@ -75890,6 +76020,7 @@ function ac_confirmed(gobj, event, kw, src) {
75890
76020
  if (refuse_if_readonly(gobj, event)) return -1;
75891
76021
  if (reload_first(gobj, event)) return -1;
75892
76022
  if (built_on_a_replaced_model(gobj, event, kw)) return -1;
76023
+ if (asked_on_another_screen(gobj, event, kw)) return -1;
75893
76024
  if (kw.what === "import") return run_import(gobj);
75894
76025
  if (kw.what === "topic") {
75895
76026
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);