@yuneta/gobj-ui 7.25.21 → 7.25.23

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
@@ -597,9 +597,15 @@ Two layout facts the browser taught this component, both worth keeping:
597
597
  session, or refused by the transport on its way out, never reaches the
598
598
  backend, so no answer will ever come back for it -- the host answers it
599
599
  itself (`C_YUI_TREEDB_TOPICS` / `C_YUI_TREEDB_GRAPH` since `7.25.21`: `i18n:
600
- "no session"`, or the transport's refusal as `error`). While a path is
601
- pending the viewer ignores every click on it, so a drill left unanswered
602
- is a dead stub for the life of the viewer.
600
+ "no session"`, or the transport's refusal as `error`). So is a drill
601
+ already SENT when the session drops: a transport answers nothing in flight
602
+ on a close, so the host keeps the paths it asked and, on the disconnect
603
+ edge (`EV_TRANSPORT_STATE` or the shell's `EV_CONNECTION_STATE`, both
604
+ `{connected: false}`), answers each one `i18n: "the connection dropped"`
605
+ (since `7.25.22`); a failure that lands after that answer is dropped with
606
+ a warning, not shown twice. While a path is pending the viewer ignores
607
+ every click on it, so a drill left unanswered is a dead stub for the life
608
+ of the viewer.
603
609
  A host that cannot read a subtree says so with the error, as
604
610
  `C_YUI_TREEDB_TOPIC_WITH_FORM` does for its schema, cell and table viewers
605
611
  since `7.25.19` (before, it declared nothing: *"Event NOT DEFINED"*):
@@ -11140,8 +11140,8 @@ function ac_load_data$2(gobj, event, kw, src) {
11140
11140
  ******************************************************/
11141
11141
  function ac_add_serie(gobj, event, kw, src) {
11142
11142
  let uplot$4 = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "uplot");
11143
- let idx;
11144
- for (idx = 0; idx < uplot$4.series.length; idx++) {
11143
+ let idx = 0;
11144
+ for (; idx < uplot$4.series.length; idx++) {
11145
11145
  let serie = uplot$4.series[idx];
11146
11146
  if (serie.id && serie.id === kw.id) {
11147
11147
  (0, _yuneta_gobj_js.log_error)("Serie '" + kw.id + "' already exists");
@@ -23791,7 +23791,8 @@ var PRIVATE_DATA$6 = {
23791
23791
  schema_gobj: null,
23792
23792
  json_gobj: null,
23793
23793
  json_win: null,
23794
- json_modal: null
23794
+ json_modal: null,
23795
+ drills_in_flight: null
23795
23796
  };
23796
23797
  var __gclass__$6 = null;
23797
23798
  /******************************
@@ -23804,6 +23805,7 @@ function mt_create$6(gobj) {
23804
23805
  gobj.priv.form_writes = {};
23805
23806
  gobj.priv.reread_on_session = false;
23806
23807
  gobj.priv.conn_shell = null;
23808
+ gobj.priv.drills_in_flight = /* @__PURE__ */ new Set();
23807
23809
  build_ui$6(gobj);
23808
23810
  let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
23809
23811
  if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
@@ -25102,6 +25104,7 @@ function close_json_viewer$1(gobj) {
25102
25104
  priv.json_gobj = null;
25103
25105
  priv.json_win = null;
25104
25106
  priv.json_modal = null;
25107
+ priv.drills_in_flight.clear();
25105
25108
  if (win && (0, _yuneta_gobj_js.is_gobj)(win)) try {
25106
25109
  if ((0, _yuneta_gobj_js.gobj_is_running)(win)) (0, _yuneta_gobj_js.gobj_stop)(win);
25107
25110
  (0, _yuneta_gobj_js.gobj_destroy)(win);
@@ -25183,7 +25186,25 @@ function request_print_tranger$1(gobj, path) {
25183
25186
  if (ret) {
25184
25187
  (0, _yuneta_gobj_js.log_error)(ret);
25185
25188
  answer_print_tranger_refused$1(gobj, path, { error: refusal_text$1(ret) });
25189
+ return;
25186
25190
  }
25191
+ if (path) priv.drills_in_flight.add(path);
25192
+ }
25193
+ /************************************************************
25194
+ * The session dropped: a drill already sent will never be answered
25195
+ * (the transport answers nothing in flight on a close), and its stub
25196
+ * would show "loading" for the life of the viewer. Answered here, so a
25197
+ * click once the session is back asks again. Idempotent: both edges of
25198
+ * one drop may arrive.
25199
+ ************************************************************/
25200
+ function answer_drills_in_flight$1(gobj) {
25201
+ let priv = gobj.priv;
25202
+ let paths = Array.from(priv.drills_in_flight);
25203
+ priv.drills_in_flight.clear();
25204
+ paths.forEach((path) => {
25205
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: transport closed, the drill '${path}' of the raw json is lost`);
25206
+ answer_print_tranger_refused$1(gobj, path, { i18n: "the connection dropped" });
25207
+ });
25187
25208
  }
25188
25209
  /***************************************************************
25189
25210
  * The last gate before a write leaves for the yuno. The per-topic
@@ -25225,6 +25246,12 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25225
25246
  let jv = gobj.priv.json_gobj;
25226
25247
  if (!jv || !(0, _yuneta_gobj_js.is_gobj)(jv) || (0, _yuneta_gobj_js.gobj_is_destroying)(jv)) return 0;
25227
25248
  let path = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "path", "", 0);
25249
+ if (path) {
25250
+ if (!gobj.priv.drills_in_flight.delete(path) && result < 0) {
25251
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: late failure of the drill '${path}' dropped, it was already answered: ${comment || ""}`);
25252
+ return 0;
25253
+ }
25254
+ } else gobj.priv.drills_in_flight.clear();
25228
25255
  if (result < 0) {
25229
25256
  if (path) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
25230
25257
  path,
@@ -25483,6 +25510,7 @@ function ac_transport_state$2(gobj, event, kw, src) {
25483
25510
  refresh_toolbar_buttons(gobj, connected);
25484
25511
  if (!connected) {
25485
25512
  gobj.priv.reread_on_session = true;
25513
+ answer_drills_in_flight$1(gobj);
25486
25514
  abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
25487
25515
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
25488
25516
  answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
@@ -25876,6 +25904,7 @@ function ac_json_closed$1(gobj, event, kw, src) {
25876
25904
  priv.json_gobj = null;
25877
25905
  priv.json_win = null;
25878
25906
  priv.json_modal = null;
25907
+ priv.drills_in_flight.clear();
25879
25908
  if (jv && (0, _yuneta_gobj_js.is_gobj)(jv)) try {
25880
25909
  (0, _yuneta_gobj_js.gobj_stop)(jv);
25881
25910
  (0, _yuneta_gobj_js.gobj_destroy)(jv);
@@ -32817,8 +32846,6 @@ var eventemitter3_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJS
32817
32846
  if ("undefined" !== typeof module) module.exports = EventEmitter;
32818
32847
  })))(), 1)).default;
32819
32848
  var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array;
32820
- Math.PI / 180;
32821
- 180 / Math.PI;
32822
32849
  //#endregion
32823
32850
  //#region node_modules/gl-matrix/esm/mat3.js
32824
32851
  /**
@@ -50093,12 +50120,12 @@ PERFORMANCE OF THIS SOFTWARE.
50093
50120
  },
50094
50121
  trys: [],
50095
50122
  ops: []
50096
- }, f, y, t, g;
50097
- return g = {
50123
+ }, f, y, t, g = {
50098
50124
  next: verb(0),
50099
50125
  "throw": verb(1),
50100
50126
  "return": verb(2)
50101
- }, typeof Symbol === "function" && (g[Symbol.iterator] = function() {
50127
+ };
50128
+ return typeof Symbol === "function" && (g[Symbol.iterator] = function() {
50102
50129
  return this;
50103
50130
  }), g;
50104
50131
  function verb(n) {
@@ -67468,6 +67495,7 @@ var PRIVATE_DATA$3 = {
67468
67495
  json_gobj: null,
67469
67496
  json_win: null,
67470
67497
  json_modal: null,
67498
+ drills_in_flight: null,
67471
67499
  with_treedb_tables: false,
67472
67500
  canvas_id: null,
67473
67501
  operation_mode: null,
@@ -67487,6 +67515,7 @@ var __gclass__$3 = null;
67487
67515
  ***************************************************************/
67488
67516
  function mt_create$3(gobj) {
67489
67517
  let priv = gobj.priv;
67518
+ priv.drills_in_flight = /* @__PURE__ */ new Set();
67490
67519
  let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
67491
67520
  if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
67492
67521
  (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
@@ -67645,9 +67674,13 @@ function refresh_raw_json_button(gobj, connected) {
67645
67674
  * "Raw JSON" button disables the moment the session drops and re-enables on
67646
67675
  * reconnect. The library view must not subscribe to the C_IEVENT_CLI itself
67647
67676
  * (that forwards the subscription upstream and breaks the session).
67677
+ * On the drop, the drills of the raw-json viewer still in flight are
67678
+ * answered: the transport answers nothing in flight on a close.
67648
67679
  ************************************************************/
67649
67680
  function ac_transport_state$1(gobj, event, kw, src) {
67650
- refresh_raw_json_button(gobj, !!(kw && kw.connected));
67681
+ let connected = !!(kw && kw.connected);
67682
+ refresh_raw_json_button(gobj, connected);
67683
+ if (!connected) answer_drills_in_flight(gobj);
67651
67684
  return 0;
67652
67685
  }
67653
67686
  /************************************************************
@@ -68350,6 +68383,7 @@ function close_json_viewer(gobj) {
68350
68383
  priv.json_gobj = null;
68351
68384
  priv.json_win = null;
68352
68385
  priv.json_modal = null;
68386
+ priv.drills_in_flight.clear();
68353
68387
  if (win && (0, _yuneta_gobj_js.is_gobj)(win)) try {
68354
68388
  if ((0, _yuneta_gobj_js.gobj_is_running)(win)) (0, _yuneta_gobj_js.gobj_stop)(win);
68355
68389
  (0, _yuneta_gobj_js.gobj_destroy)(win);
@@ -68417,7 +68451,25 @@ function request_print_tranger(gobj, path) {
68417
68451
  if (ret) {
68418
68452
  (0, _yuneta_gobj_js.log_error)(ret);
68419
68453
  answer_print_tranger_refused(gobj, path, { error: refusal_text(ret) });
68454
+ return;
68420
68455
  }
68456
+ if (path) priv.drills_in_flight.add(path);
68457
+ }
68458
+ /************************************************************
68459
+ * The session dropped: a drill already sent will never be answered
68460
+ * (the transport answers nothing in flight on a close), and its stub
68461
+ * would show "loading" for the life of the viewer. Answered here, so a
68462
+ * click once the session is back asks again. Idempotent: both edges of
68463
+ * one drop may arrive.
68464
+ ************************************************************/
68465
+ function answer_drills_in_flight(gobj) {
68466
+ let priv = gobj.priv;
68467
+ let paths = Array.from(priv.drills_in_flight);
68468
+ priv.drills_in_flight.clear();
68469
+ paths.forEach((path) => {
68470
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: transport closed, the drill '${path}' of the raw json is lost`);
68471
+ answer_print_tranger_refused(gobj, path, { i18n: "the connection dropped" });
68472
+ });
68421
68473
  }
68422
68474
  /************************************************************
68423
68475
  * Populate layout dropdown from child's available layouts
@@ -68795,6 +68847,12 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68795
68847
  let jv = priv.json_gobj;
68796
68848
  if (!jv || !(0, _yuneta_gobj_js.is_gobj)(jv) || (0, _yuneta_gobj_js.gobj_is_destroying)(jv)) return 0;
68797
68849
  let path = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "path", "", 0);
68850
+ if (path) {
68851
+ if (!priv.drills_in_flight.delete(path) && result < 0) {
68852
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: late failure of the drill '${path}' dropped, it was already answered: ${comment || ""}`);
68853
+ return 0;
68854
+ }
68855
+ } else priv.drills_in_flight.clear();
68798
68856
  if (result < 0) {
68799
68857
  if (path) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
68800
68858
  path,
@@ -68981,6 +69039,7 @@ function ac_json_closed(gobj, event, kw, src) {
68981
69039
  priv.json_gobj = null;
68982
69040
  priv.json_win = null;
68983
69041
  priv.json_modal = null;
69042
+ priv.drills_in_flight.clear();
68984
69043
  if (jv && (0, _yuneta_gobj_js.is_gobj)(jv)) try {
68985
69044
  (0, _yuneta_gobj_js.gobj_stop)(jv);
68986
69045
  (0, _yuneta_gobj_js.gobj_destroy)(jv);
@@ -69271,8 +69330,9 @@ function ac_set_operation_mode(gobj, event, kw, src) {
69271
69330
  var LEGEND_GLYPH_STYLE = `font-size:${YUI_TOOLBAR_ICON_SIZE}; line-height:1; color:inherit;`;
69272
69331
  var LEGEND_NAME_STYLE = "font-size:1rem; line-height:1.2;";
69273
69332
  var LEGEND_COUNT_STYLE = "margin-left:.4rem; font-size:.875rem; opacity:.8;";
69274
- var LEGEND_MAIN_NAME_STYLE = "font-size:1rem; line-height:1.2; font-weight:700;";
69333
+ var LEGEND_MAIN_NAME_STYLE = LEGEND_NAME_STYLE + " font-weight:700;";
69275
69334
  var LEGEND_STAR_MAIN_STYLE = LEGEND_GLYPH_STYLE + " color:var(--bulma-warning-on-scheme, var(--bulma-warning, #ffdd57));";
69335
+ var LEGEND_FOCUSED_STYLE = "box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);";
69276
69336
  function refresh_legend(gobj) {
69277
69337
  let priv = gobj.priv;
69278
69338
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
@@ -69314,7 +69374,7 @@ function refresh_legend(gobj) {
69314
69374
  let body_attrs = {
69315
69375
  class: "GRAPH_LEGEND_ITEM button" + (focused ? " GRAPH_LEGEND_FOCUSED" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
69316
69376
  type: "button",
69317
- style: "gap:0; flex:0 0 auto;" + (focused ? " box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);" : ""),
69377
+ style: "gap:0; flex:0 0 auto;" + (focused ? " " + LEGEND_FOCUSED_STYLE : ""),
69318
69378
  title: (0, i18next.t)(body_key),
69319
69379
  "data-i18n-title": body_key,
69320
69380
  "aria-label": topic_name,
@@ -69346,7 +69406,7 @@ function refresh_legend(gobj) {
69346
69406
  {
69347
69407
  class: "GRAPH_LEGEND_STAR button",
69348
69408
  type: "button",
69349
- style: "padding:0 .5rem; box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);",
69409
+ style: "padding:0 .5rem; " + LEGEND_FOCUSED_STYLE,
69350
69410
  title: (0, i18next.t)("main topic"),
69351
69411
  "data-i18n-title": "main topic",
69352
69412
  "aria-label": (0, i18next.t)("main topic"),
@@ -11135,8 +11135,8 @@ function ac_load_data$2(gobj, event, kw, src) {
11135
11135
  ******************************************************/
11136
11136
  function ac_add_serie(gobj, event, kw, src) {
11137
11137
  let uplot = gobj_read_attr(gobj, "uplot");
11138
- let idx;
11139
- for (idx = 0; idx < uplot.series.length; idx++) {
11138
+ let idx = 0;
11139
+ for (; idx < uplot.series.length; idx++) {
11140
11140
  let serie = uplot.series[idx];
11141
11141
  if (serie.id && serie.id === kw.id) {
11142
11142
  log_error("Serie '" + kw.id + "' already exists");
@@ -23786,7 +23786,8 @@ var PRIVATE_DATA$6 = {
23786
23786
  schema_gobj: null,
23787
23787
  json_gobj: null,
23788
23788
  json_win: null,
23789
- json_modal: null
23789
+ json_modal: null,
23790
+ drills_in_flight: null
23790
23791
  };
23791
23792
  var __gclass__$6 = null;
23792
23793
  /******************************
@@ -23799,6 +23800,7 @@ function mt_create$6(gobj) {
23799
23800
  gobj.priv.form_writes = {};
23800
23801
  gobj.priv.reread_on_session = false;
23801
23802
  gobj.priv.conn_shell = null;
23803
+ gobj.priv.drills_in_flight = /* @__PURE__ */ new Set();
23802
23804
  build_ui$6(gobj);
23803
23805
  let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
23804
23806
  if (!subscriber) subscriber = gobj_parent(gobj);
@@ -25097,6 +25099,7 @@ function close_json_viewer$1(gobj) {
25097
25099
  priv.json_gobj = null;
25098
25100
  priv.json_win = null;
25099
25101
  priv.json_modal = null;
25102
+ priv.drills_in_flight.clear();
25100
25103
  if (win && is_gobj(win)) try {
25101
25104
  if (gobj_is_running(win)) gobj_stop(win);
25102
25105
  gobj_destroy(win);
@@ -25178,7 +25181,25 @@ function request_print_tranger$1(gobj, path) {
25178
25181
  if (ret) {
25179
25182
  log_error(ret);
25180
25183
  answer_print_tranger_refused$1(gobj, path, { error: refusal_text$1(ret) });
25184
+ return;
25181
25185
  }
25186
+ if (path) priv.drills_in_flight.add(path);
25187
+ }
25188
+ /************************************************************
25189
+ * The session dropped: a drill already sent will never be answered
25190
+ * (the transport answers nothing in flight on a close), and its stub
25191
+ * would show "loading" for the life of the viewer. Answered here, so a
25192
+ * click once the session is back asks again. Idempotent: both edges of
25193
+ * one drop may arrive.
25194
+ ************************************************************/
25195
+ function answer_drills_in_flight$1(gobj) {
25196
+ let priv = gobj.priv;
25197
+ let paths = Array.from(priv.drills_in_flight);
25198
+ priv.drills_in_flight.clear();
25199
+ paths.forEach((path) => {
25200
+ log_warning(`${gobj_short_name(gobj)}: transport closed, the drill '${path}' of the raw json is lost`);
25201
+ answer_print_tranger_refused$1(gobj, path, { i18n: "the connection dropped" });
25202
+ });
25182
25203
  }
25183
25204
  /***************************************************************
25184
25205
  * The last gate before a write leaves for the yuno. The per-topic
@@ -25220,6 +25241,12 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25220
25241
  let jv = gobj.priv.json_gobj;
25221
25242
  if (!jv || !is_gobj(jv) || gobj_is_destroying(jv)) return 0;
25222
25243
  let path = kw_get_str(gobj, kw_command, "path", "", 0);
25244
+ if (path) {
25245
+ if (!gobj.priv.drills_in_flight.delete(path) && result < 0) {
25246
+ log_warning(`${gobj_short_name(gobj)}: late failure of the drill '${path}' dropped, it was already answered: ${comment || ""}`);
25247
+ return 0;
25248
+ }
25249
+ } else gobj.priv.drills_in_flight.clear();
25223
25250
  if (result < 0) {
25224
25251
  if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
25225
25252
  path,
@@ -25478,6 +25505,7 @@ function ac_transport_state$2(gobj, event, kw, src) {
25478
25505
  refresh_toolbar_buttons(gobj, connected);
25479
25506
  if (!connected) {
25480
25507
  gobj.priv.reread_on_session = true;
25508
+ answer_drills_in_flight$1(gobj);
25481
25509
  abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
25482
25510
  log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
25483
25511
  answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
@@ -25871,6 +25899,7 @@ function ac_json_closed$1(gobj, event, kw, src) {
25871
25899
  priv.json_gobj = null;
25872
25900
  priv.json_win = null;
25873
25901
  priv.json_modal = null;
25902
+ priv.drills_in_flight.clear();
25874
25903
  if (jv && is_gobj(jv)) try {
25875
25904
  gobj_stop(jv);
25876
25905
  gobj_destroy(jv);
@@ -32812,8 +32841,6 @@ var eventemitter3_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJS
32812
32841
  if ("undefined" !== typeof module) module.exports = EventEmitter;
32813
32842
  })))(), 1)).default;
32814
32843
  var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array;
32815
- Math.PI / 180;
32816
- 180 / Math.PI;
32817
32844
  //#endregion
32818
32845
  //#region node_modules/gl-matrix/esm/mat3.js
32819
32846
  /**
@@ -50088,12 +50115,12 @@ PERFORMANCE OF THIS SOFTWARE.
50088
50115
  },
50089
50116
  trys: [],
50090
50117
  ops: []
50091
- }, f, y, t, g;
50092
- return g = {
50118
+ }, f, y, t, g = {
50093
50119
  next: verb(0),
50094
50120
  "throw": verb(1),
50095
50121
  "return": verb(2)
50096
- }, typeof Symbol === "function" && (g[Symbol.iterator] = function() {
50122
+ };
50123
+ return typeof Symbol === "function" && (g[Symbol.iterator] = function() {
50097
50124
  return this;
50098
50125
  }), g;
50099
50126
  function verb(n) {
@@ -67463,6 +67490,7 @@ var PRIVATE_DATA$3 = {
67463
67490
  json_gobj: null,
67464
67491
  json_win: null,
67465
67492
  json_modal: null,
67493
+ drills_in_flight: null,
67466
67494
  with_treedb_tables: false,
67467
67495
  canvas_id: null,
67468
67496
  operation_mode: null,
@@ -67482,6 +67510,7 @@ var __gclass__$3 = null;
67482
67510
  ***************************************************************/
67483
67511
  function mt_create$3(gobj) {
67484
67512
  let priv = gobj.priv;
67513
+ priv.drills_in_flight = /* @__PURE__ */ new Set();
67485
67514
  let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
67486
67515
  if (!subscriber) subscriber = gobj_parent(gobj);
67487
67516
  gobj_subscribe_event(gobj, null, {}, subscriber);
@@ -67640,9 +67669,13 @@ function refresh_raw_json_button(gobj, connected) {
67640
67669
  * "Raw JSON" button disables the moment the session drops and re-enables on
67641
67670
  * reconnect. The library view must not subscribe to the C_IEVENT_CLI itself
67642
67671
  * (that forwards the subscription upstream and breaks the session).
67672
+ * On the drop, the drills of the raw-json viewer still in flight are
67673
+ * answered: the transport answers nothing in flight on a close.
67643
67674
  ************************************************************/
67644
67675
  function ac_transport_state$1(gobj, event, kw, src) {
67645
- refresh_raw_json_button(gobj, !!(kw && kw.connected));
67676
+ let connected = !!(kw && kw.connected);
67677
+ refresh_raw_json_button(gobj, connected);
67678
+ if (!connected) answer_drills_in_flight(gobj);
67646
67679
  return 0;
67647
67680
  }
67648
67681
  /************************************************************
@@ -68345,6 +68378,7 @@ function close_json_viewer(gobj) {
68345
68378
  priv.json_gobj = null;
68346
68379
  priv.json_win = null;
68347
68380
  priv.json_modal = null;
68381
+ priv.drills_in_flight.clear();
68348
68382
  if (win && is_gobj(win)) try {
68349
68383
  if (gobj_is_running(win)) gobj_stop(win);
68350
68384
  gobj_destroy(win);
@@ -68412,7 +68446,25 @@ function request_print_tranger(gobj, path) {
68412
68446
  if (ret) {
68413
68447
  log_error(ret);
68414
68448
  answer_print_tranger_refused(gobj, path, { error: refusal_text(ret) });
68449
+ return;
68415
68450
  }
68451
+ if (path) priv.drills_in_flight.add(path);
68452
+ }
68453
+ /************************************************************
68454
+ * The session dropped: a drill already sent will never be answered
68455
+ * (the transport answers nothing in flight on a close), and its stub
68456
+ * would show "loading" for the life of the viewer. Answered here, so a
68457
+ * click once the session is back asks again. Idempotent: both edges of
68458
+ * one drop may arrive.
68459
+ ************************************************************/
68460
+ function answer_drills_in_flight(gobj) {
68461
+ let priv = gobj.priv;
68462
+ let paths = Array.from(priv.drills_in_flight);
68463
+ priv.drills_in_flight.clear();
68464
+ paths.forEach((path) => {
68465
+ log_warning(`${gobj_short_name(gobj)}: transport closed, the drill '${path}' of the raw json is lost`);
68466
+ answer_print_tranger_refused(gobj, path, { i18n: "the connection dropped" });
68467
+ });
68416
68468
  }
68417
68469
  /************************************************************
68418
68470
  * Populate layout dropdown from child's available layouts
@@ -68790,6 +68842,12 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68790
68842
  let jv = priv.json_gobj;
68791
68843
  if (!jv || !is_gobj(jv) || gobj_is_destroying(jv)) return 0;
68792
68844
  let path = kw_get_str(gobj, kw_command, "path", "", 0);
68845
+ if (path) {
68846
+ if (!priv.drills_in_flight.delete(path) && result < 0) {
68847
+ log_warning(`${gobj_short_name(gobj)}: late failure of the drill '${path}' dropped, it was already answered: ${comment || ""}`);
68848
+ return 0;
68849
+ }
68850
+ } else priv.drills_in_flight.clear();
68793
68851
  if (result < 0) {
68794
68852
  if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
68795
68853
  path,
@@ -68976,6 +69034,7 @@ function ac_json_closed(gobj, event, kw, src) {
68976
69034
  priv.json_gobj = null;
68977
69035
  priv.json_win = null;
68978
69036
  priv.json_modal = null;
69037
+ priv.drills_in_flight.clear();
68979
69038
  if (jv && is_gobj(jv)) try {
68980
69039
  gobj_stop(jv);
68981
69040
  gobj_destroy(jv);
@@ -69266,8 +69325,9 @@ function ac_set_operation_mode(gobj, event, kw, src) {
69266
69325
  var LEGEND_GLYPH_STYLE = `font-size:${YUI_TOOLBAR_ICON_SIZE}; line-height:1; color:inherit;`;
69267
69326
  var LEGEND_NAME_STYLE = "font-size:1rem; line-height:1.2;";
69268
69327
  var LEGEND_COUNT_STYLE = "margin-left:.4rem; font-size:.875rem; opacity:.8;";
69269
- var LEGEND_MAIN_NAME_STYLE = "font-size:1rem; line-height:1.2; font-weight:700;";
69328
+ var LEGEND_MAIN_NAME_STYLE = LEGEND_NAME_STYLE + " font-weight:700;";
69270
69329
  var LEGEND_STAR_MAIN_STYLE = LEGEND_GLYPH_STYLE + " color:var(--bulma-warning-on-scheme, var(--bulma-warning, #ffdd57));";
69330
+ var LEGEND_FOCUSED_STYLE = "box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);";
69271
69331
  function refresh_legend(gobj) {
69272
69332
  let priv = gobj.priv;
69273
69333
  let $container = gobj_read_attr(gobj, "$container");
@@ -69309,7 +69369,7 @@ function refresh_legend(gobj) {
69309
69369
  let body_attrs = {
69310
69370
  class: "GRAPH_LEGEND_ITEM button" + (focused ? " GRAPH_LEGEND_FOCUSED" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
69311
69371
  type: "button",
69312
- style: "gap:0; flex:0 0 auto;" + (focused ? " box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);" : ""),
69372
+ style: "gap:0; flex:0 0 auto;" + (focused ? " " + LEGEND_FOCUSED_STYLE : ""),
69313
69373
  title: t(body_key),
69314
69374
  "data-i18n-title": body_key,
69315
69375
  "aria-label": topic_name,
@@ -69341,7 +69401,7 @@ function refresh_legend(gobj) {
69341
69401
  {
69342
69402
  class: "GRAPH_LEGEND_STAR button",
69343
69403
  type: "button",
69344
- style: "padding:0 .5rem; box-shadow: inset 0 0 0 2px var(--bulma-text, #2e333d);",
69404
+ style: "padding:0 .5rem; " + LEGEND_FOCUSED_STYLE,
69345
69405
  title: t("main topic"),
69346
69406
  "data-i18n-title": "main topic",
69347
69407
  "aria-label": t("main topic"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.21",
3
+ "version": "7.25.23",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -30,8 +30,8 @@
30
30
  "devDependencies": {
31
31
  "@rollup/plugin-terser": "^1.0.0",
32
32
  "@vitest/coverage-v8": "^4.1.10",
33
- "@yuneta/gobj-js": "^7.25.7",
34
- "vite": "^8.2.1",
33
+ "@yuneta/gobj-js": "^7.25.8",
34
+ "vite": "^8.3.1",
35
35
  "vitest": "^4.1.10"
36
36
  },
37
37
  "peerDependencies": {
@@ -39,7 +39,7 @@
39
39
  "@yuneta/gobj-js": "^7.22.2",
40
40
  "bulma": "^1.0.4",
41
41
  "i18next": "^26.3.6",
42
- "maplibre-gl": "^6.10.0",
42
+ "maplibre-gl": "^6.11.2",
43
43
  "tabulator-tables": "^6.5.3",
44
44
  "tom-select": "^2.6.2",
45
45
  "uplot": "^1.6.32",
@@ -183,6 +183,7 @@ let PRIVATE_DATA = {
183
183
  json_gobj: null, /* C_YUI_JSON viewer of the raw tranger (or null) */
184
184
  json_win: null, /* C_YUI_WINDOW hosting it, desktop (or null) */
185
185
  json_modal: null, /* shell modal hosting it, mobile (or null) */
186
+ drills_in_flight: null, /* Set of the viewer's drilled paths sent, not answered */
186
187
  with_treedb_tables: false,
187
188
  canvas_id: null,
188
189
  operation_mode: null,
@@ -214,6 +215,10 @@ function mt_create(gobj)
214
215
  {
215
216
  let priv = gobj.priv;
216
217
 
218
+ /* The drills of the raw-json viewer sent and not yet answered:
219
+ * answered on the disconnect edge (answer_drills_in_flight). */
220
+ priv.drills_in_flight = new Set();
221
+
217
222
  /*
218
223
  * CHILD subscription model
219
224
  */
@@ -474,10 +479,16 @@ function refresh_raw_json_button(gobj, connected)
474
479
  * "Raw JSON" button disables the moment the session drops and re-enables on
475
480
  * reconnect. The library view must not subscribe to the C_IEVENT_CLI itself
476
481
  * (that forwards the subscription upstream and breaks the session).
482
+ * On the drop, the drills of the raw-json viewer still in flight are
483
+ * answered: the transport answers nothing in flight on a close.
477
484
  ************************************************************/
478
485
  function ac_transport_state(gobj, event, kw, src)
479
486
  {
480
- refresh_raw_json_button(gobj, !!(kw && kw.connected));
487
+ let connected = !!(kw && kw.connected);
488
+ refresh_raw_json_button(gobj, connected);
489
+ if(!connected) {
490
+ answer_drills_in_flight(gobj);
491
+ }
481
492
  return 0;
482
493
  }
483
494
 
@@ -1270,6 +1281,7 @@ function close_json_viewer(gobj)
1270
1281
  priv.json_gobj = null;
1271
1282
  priv.json_win = null;
1272
1283
  priv.json_modal = null;
1284
+ priv.drills_in_flight.clear();
1273
1285
 
1274
1286
  if(win && is_gobj(win)) {
1275
1287
  try {
@@ -1381,9 +1393,32 @@ function request_print_tranger(gobj, path)
1381
1393
  if(ret) {
1382
1394
  log_error(ret);
1383
1395
  answer_print_tranger_refused(gobj, path, {error: refusal_text(ret)});
1396
+ return;
1397
+ }
1398
+ if(path) {
1399
+ priv.drills_in_flight.add(path);
1384
1400
  }
1385
1401
  }
1386
1402
 
1403
+ /************************************************************
1404
+ * The session dropped: a drill already sent will never be answered
1405
+ * (the transport answers nothing in flight on a close), and its stub
1406
+ * would show "loading" for the life of the viewer. Answered here, so a
1407
+ * click once the session is back asks again. Idempotent: both edges of
1408
+ * one drop may arrive.
1409
+ ************************************************************/
1410
+ function answer_drills_in_flight(gobj)
1411
+ {
1412
+ let priv = gobj.priv;
1413
+ let paths = Array.from(priv.drills_in_flight);
1414
+ priv.drills_in_flight.clear();
1415
+ paths.forEach((path) => {
1416
+ log_warning(`${gobj_short_name(gobj)}: transport closed, the drill ` +
1417
+ `'${path}' of the raw json is lost`);
1418
+ answer_print_tranger_refused(gobj, path, {i18n: "the connection dropped"});
1419
+ });
1420
+ }
1421
+
1387
1422
  /************************************************************
1388
1423
  * Populate layout dropdown from child's available layouts
1389
1424
  ************************************************************/
@@ -2100,6 +2135,19 @@ function ac_mt_command_answer(gobj, event, kw, src)
2100
2135
  return 0; /* viewer closed before its answer landed: benign */
2101
2136
  }
2102
2137
  let path = kw_get_str(gobj, kw_command, "path", "", 0);
2138
+ if(path) {
2139
+ let in_flight = priv.drills_in_flight.delete(path);
2140
+ if(!in_flight && result < 0) {
2141
+ /* Already answered (the drop answered it, or the viewer
2142
+ * was refilled): a second answer would log it twice. */
2143
+ log_warning(`${gobj_short_name(gobj)}: late failure of the drill ` +
2144
+ `'${path}' dropped, it was already answered: ${comment || ""}`);
2145
+ return 0;
2146
+ }
2147
+ } else {
2148
+ /* A whole document replaces every stub the drills were for. */
2149
+ priv.drills_in_flight.clear();
2150
+ }
2103
2151
  if(result < 0) {
2104
2152
  if(path) {
2105
2153
  gobj_send_event(jv, "EV_SUBTREE_ERROR",
@@ -2411,6 +2459,7 @@ function ac_json_closed(gobj, event, kw, src)
2411
2459
  priv.json_gobj = null;
2412
2460
  priv.json_win = null;
2413
2461
  priv.json_modal = null;
2462
+ priv.drills_in_flight.clear();
2414
2463
  if(jv && is_gobj(jv)) {
2415
2464
  try {
2416
2465
  /* STOP before destroy — the viewer was STARTED in open_json_viewer
@@ -137,6 +137,7 @@ let PRIVATE_DATA = {
137
137
  json_gobj: null, /* C_YUI_JSON raw-tranger viewer (or null) */
138
138
  json_win: null, /* C_YUI_WINDOW hosting it, desktop (or null) */
139
139
  json_modal: null, /* shell modal hosting it, mobile (or null) */
140
+ drills_in_flight: null, /* Set of the viewer's drilled paths sent, not answered */
140
141
  };
141
142
 
142
143
  let __gclass__ = null;
@@ -166,6 +167,9 @@ function mt_create(gobj)
166
167
  * (reread_open_topics), never out of session. */
167
168
  gobj.priv.reread_on_session = false;
168
169
  gobj.priv.conn_shell = null; /* the shell whose EV_CONNECTION_STATE we hear */
170
+ /* The drills of the raw-json viewer sent and not yet answered:
171
+ * answered on the disconnect edge (answer_drills_in_flight). */
172
+ gobj.priv.drills_in_flight = new Set();
169
173
 
170
174
  build_ui(gobj);
171
175
 
@@ -1887,6 +1891,7 @@ function close_json_viewer(gobj)
1887
1891
  priv.json_gobj = null;
1888
1892
  priv.json_win = null;
1889
1893
  priv.json_modal = null;
1894
+ priv.drills_in_flight.clear();
1890
1895
 
1891
1896
  if(win && is_gobj(win)) {
1892
1897
  try {
@@ -2011,9 +2016,32 @@ function request_print_tranger(gobj, path)
2011
2016
  if(ret) {
2012
2017
  log_error(ret);
2013
2018
  answer_print_tranger_refused(gobj, path, {error: refusal_text(ret)});
2019
+ return;
2020
+ }
2021
+ if(path) {
2022
+ priv.drills_in_flight.add(path);
2014
2023
  }
2015
2024
  }
2016
2025
 
2026
+ /************************************************************
2027
+ * The session dropped: a drill already sent will never be answered
2028
+ * (the transport answers nothing in flight on a close), and its stub
2029
+ * would show "loading" for the life of the viewer. Answered here, so a
2030
+ * click once the session is back asks again. Idempotent: both edges of
2031
+ * one drop may arrive.
2032
+ ************************************************************/
2033
+ function answer_drills_in_flight(gobj)
2034
+ {
2035
+ let priv = gobj.priv;
2036
+ let paths = Array.from(priv.drills_in_flight);
2037
+ priv.drills_in_flight.clear();
2038
+ paths.forEach((path) => {
2039
+ log_warning(`${gobj_short_name(gobj)}: transport closed, the drill ` +
2040
+ `'${path}' of the raw json is lost`);
2041
+ answer_print_tranger_refused(gobj, path, {i18n: "the connection dropped"});
2042
+ });
2043
+ }
2044
+
2017
2045
 
2018
2046
 
2019
2047
 
@@ -2080,6 +2108,19 @@ function ac_mt_command_answer(gobj, event, kw, src)
2080
2108
  return 0; /* viewer closed before its answer landed: benign */
2081
2109
  }
2082
2110
  let path = kw_get_str(gobj, kw_command, "path", "", 0);
2111
+ if(path) {
2112
+ let in_flight = gobj.priv.drills_in_flight.delete(path);
2113
+ if(!in_flight && result < 0) {
2114
+ /* Already answered (the drop answered it, or the viewer
2115
+ * was refilled): a second answer would log it twice. */
2116
+ log_warning(`${gobj_short_name(gobj)}: late failure of the drill ` +
2117
+ `'${path}' dropped, it was already answered: ${comment || ""}`);
2118
+ return 0;
2119
+ }
2120
+ } else {
2121
+ /* A whole document replaces every stub the drills were for. */
2122
+ gobj.priv.drills_in_flight.clear();
2123
+ }
2083
2124
  if(result < 0) {
2084
2125
  if(path) {
2085
2126
  gobj_send_event(jv, "EV_SUBTREE_ERROR",
@@ -2552,12 +2593,14 @@ function ac_transport_state(gobj, event, kw, src)
2552
2593
  * the backend: its form stayed open and busy for ever. Answered
2553
2594
  * refused: the form stays open on what
2554
2595
  * was typed, and the person saves again when the session is back.
2596
+ * The same for a drill of the raw-json viewer: its stub is answered.
2555
2597
  */
2556
2598
  if(!connected) {
2557
2599
  /* What happens in the store while the session is down reaches
2558
2600
  * no table: its node events are published to nobody. Every open
2559
2601
  * table is read again when the session is back. */
2560
2602
  gobj.priv.reread_on_session = true;
2603
+ answer_drills_in_flight(gobj);
2561
2604
  abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
2562
2605
  log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
2563
2606
  answer_form_write(
@@ -3125,6 +3168,7 @@ function ac_json_closed(gobj, event, kw, src)
3125
3168
  priv.json_gobj = null;
3126
3169
  priv.json_win = null;
3127
3170
  priv.json_modal = null;
3171
+ priv.drills_in_flight.clear();
3128
3172
  if(jv && is_gobj(jv)) {
3129
3173
  try {
3130
3174
  /* STOP before destroy — the viewer was STARTED in open_json_viewer
@@ -13,6 +13,12 @@
13
13
  * stayed on "loading" for the life of the viewer, and no click --
14
14
  * not even after the reconnect -- asked again.
15
15
  *
16
+ * Before gobj-ui 7.25.22 a drill already SENT when the session
17
+ * dropped was never answered either: the transport answers nothing
18
+ * in flight on a close, and neither host remembered what it had
19
+ * asked. Each host now keeps its drills in flight and answers them
20
+ * on the disconnect edge.
21
+ *
16
22
  * Driven through the real hosts and the real viewer on a document
17
23
  * double, with a fake transport whose state is the one the hosts
18
24
  * read and which refuses on demand.
@@ -229,4 +235,114 @@ for(const gclass of ["C_YUI_TREEDB_TOPICS", "C_YUI_TREEDB_GRAPH"]) {
229
235
  expect(logged.filter((l) => l.level === "error")).toEqual([]);
230
236
  });
231
237
  });
238
+
239
+ describe(`${gclass}: a drill in flight when the session drops is answered`, () => {
240
+
241
+ test("the drop answers the stub, and a click after the reconnect asks again", () => {
242
+ const {remote, host, viewer} = build(`${gclass}_d`, gclass);
243
+ click_stub(viewer);
244
+ expect(viewer.priv.pending.has("topics")).toBe(true);
245
+
246
+ /* The session drops before the answer: the transport never
247
+ * answers a command in flight. */
248
+ gobj_change_state(remote, "ST_DISCONNECTED");
249
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: false}, host);
250
+
251
+ expect(viewer.priv.pending.has("topics")).toBe(false);
252
+ expect(viewer.priv.errors.get("topics")).toEqual(
253
+ {error: "the connection dropped", is_key: true});
254
+
255
+ /* The same edge again answers nothing more. */
256
+ logged.length = 0;
257
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: false}, host);
258
+ expect(logged.filter((l) => l.level === "error")).toEqual([]);
259
+
260
+ gobj_change_state(remote, "ST_SESSION");
261
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: true}, host);
262
+ click_stub(viewer);
263
+ expect(drills()).toEqual(["topics", "topics"]);
264
+ expect(viewer.priv.pending.has("topics")).toBe(true);
265
+ });
266
+
267
+ test("a failure that lands after the drop answered is not answered twice", () => {
268
+ /* gui_agent's routing adapter answers every request in flight
269
+ * as failed on the close, and that answer can land after the
270
+ * edge that already answered the stub. */
271
+ const {remote, host, viewer} = build(`${gclass}_h`, gclass);
272
+ click_stub(viewer);
273
+ const request = commands.find((c) => c.command === "print-tranger");
274
+ gobj_change_state(remote, "ST_DISCONNECTED");
275
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: false}, host);
276
+
277
+ logged.length = 0;
278
+ gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
279
+ result: -1, comment: "the connection dropped", data: null,
280
+ __md_iev__: {
281
+ command_stack: [{command: "print-tranger", kw: request.kw.__md_command__}]
282
+ }
283
+ }, remote);
284
+ expect(viewer.priv.errors.get("topics")).toEqual(
285
+ {error: "the connection dropped", is_key: true});
286
+ expect(logged.filter((l) => l.level === "error")).toEqual([]);
287
+ });
288
+
289
+ test("a drill already answered is not answered again by a drop", () => {
290
+ const {remote, host, viewer} = build(`${gclass}_e`, gclass);
291
+ click_stub(viewer);
292
+ const request = commands.find((c) => c.command === "print-tranger");
293
+ gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
294
+ result: 0, comment: "", data: {a: 1, b: 2, c: 3},
295
+ __md_iev__: {
296
+ command_stack: [{command: "print-tranger", kw: request.kw.__md_command__}]
297
+ }
298
+ }, remote);
299
+ expect(viewer.priv.pending.has("topics")).toBe(false);
300
+ expect(viewer.priv.errors.has("topics")).toBe(false);
301
+
302
+ gobj_change_state(remote, "ST_DISCONNECTED");
303
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: false}, host);
304
+ expect(viewer.priv.errors.has("topics")).toBe(false);
305
+ expect(logged.filter((l) => l.level === "error")).toEqual([]);
306
+ });
307
+
308
+ test("a drill that failed is not answered again by a drop", () => {
309
+ const {remote, host, viewer} = build(`${gclass}_f`, gclass);
310
+ click_stub(viewer);
311
+ const request = commands.find((c) => c.command === "print-tranger");
312
+ gobj_send_event(host, "EV_MT_COMMAND_ANSWER", {
313
+ result: -1, comment: "no such path", data: null,
314
+ __md_iev__: {
315
+ command_stack: [{command: "print-tranger", kw: request.kw.__md_command__}]
316
+ }
317
+ }, remote);
318
+ expect(viewer.priv.errors.get("topics")).toEqual(
319
+ {error: "no such path", is_key: false});
320
+
321
+ gobj_change_state(remote, "ST_DISCONNECTED");
322
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: false}, host);
323
+ expect(viewer.priv.errors.get("topics")).toEqual(
324
+ {error: "no such path", is_key: false});
325
+ });
326
+ });
232
327
  }
328
+
329
+ /*
330
+ * wattyzer and yunovatios hear the edge from the SHELL
331
+ * (EV_CONNECTION_STATE), not from a host that forwards the transport.
332
+ */
333
+ describe("C_YUI_TREEDB_TOPICS: the shell's 'down' answers a drill in flight", () => {
334
+ test("EV_CONNECTION_STATE down answers the stub once, with its edge twin", () => {
335
+ const {remote, host, viewer} = build("topics_g", "C_YUI_TREEDB_TOPICS");
336
+ click_stub(viewer);
337
+
338
+ gobj_change_state(remote, "ST_DISCONNECTED");
339
+ gobj_send_event(host, "EV_CONNECTION_STATE", {connected: false}, host);
340
+ expect(viewer.priv.pending.has("topics")).toBe(false);
341
+ expect(viewer.priv.errors.get("topics")).toEqual(
342
+ {error: "the connection dropped", is_key: true});
343
+
344
+ const errors_after_first = logged.filter((l) => l.level === "error").length;
345
+ gobj_send_event(host, "EV_TRANSPORT_STATE", {connected: false}, host);
346
+ expect(logged.filter((l) => l.level === "error").length).toBe(errors_after_first);
347
+ });
348
+ });