@yuneta/gobj-ui 7.25.2 → 7.25.4

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
@@ -2021,6 +2021,22 @@ and the column screen says so in a banner; the host sends `EV_REFRESH` after a
2021
2021
  save, and the reload forgets the drafts. The export (C literal and JSON) warns
2022
2022
  while there are drafts: the literal carries the versions a save publishes.
2023
2023
 
2024
+ **A draft survives a reload** (7.25.3). The mark of a write lived in the
2025
+ session's memory only, so a reload of the page, a reconnect or a refresh of
2026
+ the model showed no draft while `__system__` still differed from the file in
2027
+ use. The host tells the editor what IS a draft, from data: `C_TREEDB`'s
2028
+ `saved-schema` answers `draft_changed` per treedb (yunetas 7.25.3+), and the
2029
+ host sends it as `EV_DRAFTS`; `drafts_of_saved_answer()` reads the unnamed
2030
+ answer into the shape the event takes. What the session wrote stays marked;
2031
+ what the host said is applied again after every reload of the model.
2032
+
2033
+ ```js
2034
+ // The host, when its saved-schema answers land (rows = the unnamed answer's data)
2035
+ import {drafts_of_saved_answer} from "@yuneta/gobj-ui/src/host_drafts.js";
2036
+ gobj_send_event(editor, "EV_DRAFTS", {drafts: drafts_of_saved_answer(rows)}, gobj);
2037
+ // -> {drafts: {treedb_authzs: ["users"]}}
2038
+ ```
2039
+
2024
2040
  What the screens offer:
2025
2041
 
2026
2042
  | Screen | What it is for |
@@ -29108,6 +29108,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
29108
29108
  switch (field_desc.real_type) {
29109
29109
  case "string":
29110
29110
  if (value && (0, _yuneta_gobj_js.is_date)(value)) value = value.toISOString();
29111
+ else if (typeof value === "number" && value > 0) value = (/* @__PURE__ */ new Date(value * 1e3)).toISOString();
29111
29112
  break;
29112
29113
  case "integer":
29113
29114
  if (value && (0, _yuneta_gobj_js.is_date)(value)) value = value.getTime() / 1e3;
@@ -29440,7 +29441,10 @@ function ac_confirmed$2(gobj, event, kw, src) {
29440
29441
  let topic_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name");
29441
29442
  if (what === "delete_selection" || what === "delete_row") {
29442
29443
  let found = yui_rows_by_ids(tabulator, Array.isArray(kw.ids) ? kw.ids : []);
29443
- if (found.missing.length) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: rows gone while confirming the delete, not deleted: ${found.missing.join(", ")}`);
29444
+ if (found.missing.length) {
29445
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: rows gone while confirming the delete, not deleted: ${found.missing.join(", ")}`);
29446
+ yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t: i18next.t });
29447
+ }
29444
29448
  if (!found.rows.length) return -1;
29445
29449
  for (let row of found.rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
29446
29450
  topic_name,
@@ -66451,6 +66455,7 @@ function ac_confirmed$1(gobj, event, kw, src) {
66451
66455
  if (what === "delete_selection") {
66452
66456
  let ids = kw && Array.isArray(kw.ids) ? kw.ids : [];
66453
66457
  let nodes = [];
66458
+ let missing = [];
66454
66459
  for (let id of ids) {
66455
66460
  let nd = null;
66456
66461
  try {
@@ -66459,6 +66464,11 @@ function ac_confirmed$1(gobj, event, kw, src) {
66459
66464
  nd = null;
66460
66465
  }
66461
66466
  if (nd && nd.data && nd.data.desc) nodes.push(nd);
66467
+ else missing.push(id);
66468
+ }
66469
+ if (missing.length) {
66470
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: cards gone while confirming the delete, not deleted: ${missing.join(", ")}`);
66471
+ yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t: i18next.t });
66462
66472
  }
66463
66473
  if (!nodes.length) {
66464
66474
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no node any more`);
@@ -70255,6 +70265,44 @@ function register_c_yui_treedb_schema() {
70255
70265
  return create_gclass$2(GCLASS_NAME$2);
70256
70266
  }
70257
70267
  //#endregion
70268
+ //#region src/host_drafts.js
70269
+ /***********************************************************************
70270
+ * host_drafts.js
70271
+ *
70272
+ * The topics a schema editor shows as DRAFTS (edited in
70273
+ * __system__, not yet saved), told by the host from data. The
70274
+ * editor marks a topic when a write of this session lands on
70275
+ * it, and that mark lived in the memory of the session only:
70276
+ * a reload of the page, a reconnect or a refresh of the model
70277
+ * lost the chip, the banner and the export warning while
70278
+ * __system__ still differed from the file in use (N13 of the
70279
+ * 2026-09-22 review). The host reads that difference from
70280
+ * C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
70281
+ * hands it here; the editor applies it to its model.
70282
+ *
70283
+ * Copyright (c) 2026, ArtGins.
70284
+ * All Rights Reserved.
70285
+ ***********************************************************************/
70286
+ /************************************************************
70287
+ * Mark in `written` the topics of `model` the host names as
70288
+ * drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
70289
+ * topic the model does not hold is ignored. Returns how many were
70290
+ * marked.
70291
+ ************************************************************/
70292
+ function mark_host_drafts(written, model, drafts) {
70293
+ let marked = 0;
70294
+ if (!written || !model || !Array.isArray(model.treedbs) || !drafts) return marked;
70295
+ for (let treedb of model.treedbs) {
70296
+ const names = drafts[treedb.id];
70297
+ if (!Array.isArray(names) || names.length === 0) continue;
70298
+ for (let topic of treedb.topics || []) if (names.includes(topic.name)) {
70299
+ written[topic.id] = true;
70300
+ marked++;
70301
+ }
70302
+ }
70303
+ return marked;
70304
+ }
70305
+ //#endregion
70258
70306
  //#region src/schema_validate.js
70259
70307
  /***********************************************************************
70260
70308
  * schema_validate.js
@@ -71981,6 +72029,7 @@ function mt_create$1(gobj) {
71981
72029
  let priv = gobj.priv;
71982
72030
  priv.baseline = {};
71983
72031
  priv.written = {};
72032
+ priv.host_drafts = {};
71984
72033
  priv.save_queue = [];
71985
72034
  build_ui$1(gobj);
71986
72035
  let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
@@ -72222,6 +72271,7 @@ function start_measuring(gobj) {
72222
72271
  priv.written = {};
72223
72272
  if (!priv.model) return;
72224
72273
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
72274
+ mark_host_drafts(priv.written, priv.model, priv.host_drafts);
72225
72275
  }
72226
72276
  /***************************************************************
72227
72277
  * "This topic was written in this session and is not saved."
@@ -74659,6 +74709,23 @@ function ac_refresh$1(gobj, event, kw, src) {
74659
74709
  return request_model(gobj);
74660
74710
  }
74661
74711
  /***************************************************************
74712
+ * {drafts: {treedb_name: [topic names]}} -- the host says which
74713
+ * topics hold a draft in __system__ that the file in use does not
74714
+ * have (C_TREEDB's saved-schema, `draft_changed`). The mark of a
74715
+ * write lived in this session's memory only, so a reload of the
74716
+ * page, a reconnect or a refresh showed no draft while __system__
74717
+ * still differed from the file (N13 of the 2026-09-22 review).
74718
+ * Replaces what the host said before; what this session wrote
74719
+ * stays marked.
74720
+ ***************************************************************/
74721
+ function ac_drafts(gobj, event, kw, src) {
74722
+ let priv = gobj.priv;
74723
+ priv.host_drafts = kw && kw.drafts && typeof kw.drafts === "object" ? kw.drafts : {};
74724
+ mark_host_drafts(priv.written, priv.model, priv.host_drafts);
74725
+ if (priv.model) render(gobj);
74726
+ return 0;
74727
+ }
74728
+ /***************************************************************
74662
74729
  * Moving about.
74663
74730
  ***************************************************************/
74664
74731
  function ac_select_treedb(gobj, event, kw, src) {
@@ -75132,6 +75199,11 @@ function create_gclass$1(gclass_name) {
75132
75199
  "EV_REFRESH",
75133
75200
  ac_refresh$1,
75134
75201
  null
75202
+ ],
75203
+ [
75204
+ "EV_DRAFTS",
75205
+ ac_drafts,
75206
+ null
75135
75207
  ]
75136
75208
  ];
75137
75209
  const TREEDB_WIDE = [
@@ -75280,6 +75352,7 @@ function create_gclass$1(gclass_name) {
75280
75352
  ["EV_TRANSPORT_STATE", 0],
75281
75353
  ["EV_LANGUAGE_CHANGED", 0],
75282
75354
  ["EV_REFRESH", 0],
75355
+ ["EV_DRAFTS", 0],
75283
75356
  ["EV_SELECT_TREEDB", 0],
75284
75357
  ["EV_SELECT_TOPIC", 0],
75285
75358
  ["EV_BACK", 0],
@@ -29103,6 +29103,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
29103
29103
  switch (field_desc.real_type) {
29104
29104
  case "string":
29105
29105
  if (value && is_date(value)) value = value.toISOString();
29106
+ else if (typeof value === "number" && value > 0) value = (/* @__PURE__ */ new Date(value * 1e3)).toISOString();
29106
29107
  break;
29107
29108
  case "integer":
29108
29109
  if (value && is_date(value)) value = value.getTime() / 1e3;
@@ -29435,7 +29436,10 @@ function ac_confirmed$2(gobj, event, kw, src) {
29435
29436
  let topic_name = gobj_read_str_attr(gobj, "topic_name");
29436
29437
  if (what === "delete_selection" || what === "delete_row") {
29437
29438
  let found = yui_rows_by_ids(tabulator, Array.isArray(kw.ids) ? kw.ids : []);
29438
- if (found.missing.length) log_error(`${gobj_short_name(gobj)}: ${event}: rows gone while confirming the delete, not deleted: ${found.missing.join(", ")}`);
29439
+ if (found.missing.length) {
29440
+ log_error(`${gobj_short_name(gobj)}: ${event}: rows gone while confirming the delete, not deleted: ${found.missing.join(", ")}`);
29441
+ yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t });
29442
+ }
29439
29443
  if (!found.rows.length) return -1;
29440
29444
  for (let row of found.rows) gobj_publish_event(gobj, "EV_DELETE_RECORD", {
29441
29445
  topic_name,
@@ -66446,6 +66450,7 @@ function ac_confirmed$1(gobj, event, kw, src) {
66446
66450
  if (what === "delete_selection") {
66447
66451
  let ids = kw && Array.isArray(kw.ids) ? kw.ids : [];
66448
66452
  let nodes = [];
66453
+ let missing = [];
66449
66454
  for (let id of ids) {
66450
66455
  let nd = null;
66451
66456
  try {
@@ -66454,6 +66459,11 @@ function ac_confirmed$1(gobj, event, kw, src) {
66454
66459
  nd = null;
66455
66460
  }
66456
66461
  if (nd && nd.data && nd.data.desc) nodes.push(nd);
66462
+ else missing.push(id);
66463
+ }
66464
+ if (missing.length) {
66465
+ log_error(`${gobj_short_name(gobj)}: ${event}: cards gone while confirming the delete, not deleted: ${missing.join(", ")}`);
66466
+ yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t });
66457
66467
  }
66458
66468
  if (!nodes.length) {
66459
66469
  log_error(`${gobj_short_name(gobj)}: ${event} names no node any more`);
@@ -70250,6 +70260,44 @@ function register_c_yui_treedb_schema() {
70250
70260
  return create_gclass$2(GCLASS_NAME$2);
70251
70261
  }
70252
70262
  //#endregion
70263
+ //#region src/host_drafts.js
70264
+ /***********************************************************************
70265
+ * host_drafts.js
70266
+ *
70267
+ * The topics a schema editor shows as DRAFTS (edited in
70268
+ * __system__, not yet saved), told by the host from data. The
70269
+ * editor marks a topic when a write of this session lands on
70270
+ * it, and that mark lived in the memory of the session only:
70271
+ * a reload of the page, a reconnect or a refresh of the model
70272
+ * lost the chip, the banner and the export warning while
70273
+ * __system__ still differed from the file in use (N13 of the
70274
+ * 2026-09-22 review). The host reads that difference from
70275
+ * C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
70276
+ * hands it here; the editor applies it to its model.
70277
+ *
70278
+ * Copyright (c) 2026, ArtGins.
70279
+ * All Rights Reserved.
70280
+ ***********************************************************************/
70281
+ /************************************************************
70282
+ * Mark in `written` the topics of `model` the host names as
70283
+ * drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
70284
+ * topic the model does not hold is ignored. Returns how many were
70285
+ * marked.
70286
+ ************************************************************/
70287
+ function mark_host_drafts(written, model, drafts) {
70288
+ let marked = 0;
70289
+ if (!written || !model || !Array.isArray(model.treedbs) || !drafts) return marked;
70290
+ for (let treedb of model.treedbs) {
70291
+ const names = drafts[treedb.id];
70292
+ if (!Array.isArray(names) || names.length === 0) continue;
70293
+ for (let topic of treedb.topics || []) if (names.includes(topic.name)) {
70294
+ written[topic.id] = true;
70295
+ marked++;
70296
+ }
70297
+ }
70298
+ return marked;
70299
+ }
70300
+ //#endregion
70253
70301
  //#region src/schema_validate.js
70254
70302
  /***********************************************************************
70255
70303
  * schema_validate.js
@@ -71976,6 +72024,7 @@ function mt_create$1(gobj) {
71976
72024
  let priv = gobj.priv;
71977
72025
  priv.baseline = {};
71978
72026
  priv.written = {};
72027
+ priv.host_drafts = {};
71979
72028
  priv.save_queue = [];
71980
72029
  build_ui$1(gobj);
71981
72030
  let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
@@ -72217,6 +72266,7 @@ function start_measuring(gobj) {
72217
72266
  priv.written = {};
72218
72267
  if (!priv.model) return;
72219
72268
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
72269
+ mark_host_drafts(priv.written, priv.model, priv.host_drafts);
72220
72270
  }
72221
72271
  /***************************************************************
72222
72272
  * "This topic was written in this session and is not saved."
@@ -74654,6 +74704,23 @@ function ac_refresh$1(gobj, event, kw, src) {
74654
74704
  return request_model(gobj);
74655
74705
  }
74656
74706
  /***************************************************************
74707
+ * {drafts: {treedb_name: [topic names]}} -- the host says which
74708
+ * topics hold a draft in __system__ that the file in use does not
74709
+ * have (C_TREEDB's saved-schema, `draft_changed`). The mark of a
74710
+ * write lived in this session's memory only, so a reload of the
74711
+ * page, a reconnect or a refresh showed no draft while __system__
74712
+ * still differed from the file (N13 of the 2026-09-22 review).
74713
+ * Replaces what the host said before; what this session wrote
74714
+ * stays marked.
74715
+ ***************************************************************/
74716
+ function ac_drafts(gobj, event, kw, src) {
74717
+ let priv = gobj.priv;
74718
+ priv.host_drafts = kw && kw.drafts && typeof kw.drafts === "object" ? kw.drafts : {};
74719
+ mark_host_drafts(priv.written, priv.model, priv.host_drafts);
74720
+ if (priv.model) render(gobj);
74721
+ return 0;
74722
+ }
74723
+ /***************************************************************
74657
74724
  * Moving about.
74658
74725
  ***************************************************************/
74659
74726
  function ac_select_treedb(gobj, event, kw, src) {
@@ -75127,6 +75194,11 @@ function create_gclass$1(gclass_name) {
75127
75194
  "EV_REFRESH",
75128
75195
  ac_refresh$1,
75129
75196
  null
75197
+ ],
75198
+ [
75199
+ "EV_DRAFTS",
75200
+ ac_drafts,
75201
+ null
75130
75202
  ]
75131
75203
  ];
75132
75204
  const TREEDB_WIDE = [
@@ -75275,6 +75347,7 @@ function create_gclass$1(gclass_name) {
75275
75347
  ["EV_TRANSPORT_STATE", 0],
75276
75348
  ["EV_LANGUAGE_CHANGED", 0],
75277
75349
  ["EV_REFRESH", 0],
75350
+ ["EV_DRAFTS", 0],
75278
75351
  ["EV_SELECT_TREEDB", 0],
75279
75352
  ["EV_SELECT_TOPIC", 0],
75280
75353
  ["EV_BACK", 0],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.2",
3
+ "version": "7.25.4",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -174,6 +174,7 @@ import {
174
174
  } from "./g6_touch_gestures.js";
175
175
  import {yui_theme_now, yui_watch_theme} from "./yui_theme.js";
176
176
  import {yui_shell_of} from "./c_yui_shell.js";
177
+ import {yui_shell_show_error} from "./shell_modals.js";
177
178
 
178
179
  /***************************************************************
179
180
  * YuiToolbar — G6 Toolbar subclass that adds per-item className
@@ -11761,6 +11762,7 @@ function ac_confirmed(gobj, event, kw, src)
11761
11762
  if(what === "delete_selection") {
11762
11763
  let ids = (kw && Array.isArray(kw.ids))? kw.ids : [];
11763
11764
  let nodes = [];
11765
+ let missing = [];
11764
11766
  for(let id of ids) {
11765
11767
  let nd = null;
11766
11768
  try {
@@ -11770,8 +11772,18 @@ function ac_confirmed(gobj, event, kw, src)
11770
11772
  }
11771
11773
  if(nd && nd.data && nd.data.desc) {
11772
11774
  nodes.push(nd);
11775
+ } else {
11776
+ missing.push(id);
11773
11777
  }
11774
11778
  }
11779
+ if(missing.length) {
11780
+ /* Gone while the question was open (another writer deleted
11781
+ * them): said, to the log and to the person (a low of the
11782
+ * 2026-09-22 review); the rest go on. */
11783
+ log_error(`${gobj_short_name(gobj)}: ${event}: cards gone while ` +
11784
+ `confirming the delete, not deleted: ${missing.join(", ")}`);
11785
+ yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", {t: t});
11786
+ }
11775
11787
  if(!nodes.length) {
11776
11788
  log_error(`${gobj_short_name(gobj)}: ${event} names no node any more`);
11777
11789
  return -1;
@@ -113,6 +113,7 @@ import {
113
113
  empty_string,
114
114
  } from "@yuneta/gobj-js";
115
115
  import {yui_tint} from "./bulma_tint.js";
116
+ import {mark_host_drafts} from "./host_drafts.js";
116
117
 
117
118
  import "./c_yui_schema_editor.css";
118
119
 
@@ -248,6 +249,7 @@ function mt_create(gobj)
248
249
 
249
250
  priv.baseline = {};
250
251
  priv.written = {};
252
+ priv.host_drafts = {}; /* what the host says is a draft, {treedb: [topics]} (EV_DRAFTS) */
251
253
  priv.save_queue = [];
252
254
 
253
255
  build_ui(gobj);
@@ -571,6 +573,9 @@ function start_measuring(gobj)
571
573
  priv.baseline[topic.id] = topic.topic_version;
572
574
  }
573
575
  }
576
+ /* A draft is a draft after a reload too: what the host said stays
577
+ * marked (host_drafts.js) until the host says otherwise. */
578
+ mark_host_drafts(priv.written, priv.model, priv.host_drafts);
574
579
  }
575
580
 
576
581
  /***************************************************************
@@ -2755,6 +2760,28 @@ function ac_refresh(gobj, event, kw, src)
2755
2760
  return request_model(gobj);
2756
2761
  }
2757
2762
 
2763
+ /***************************************************************
2764
+ * {drafts: {treedb_name: [topic names]}} -- the host says which
2765
+ * topics hold a draft in __system__ that the file in use does not
2766
+ * have (C_TREEDB's saved-schema, `draft_changed`). The mark of a
2767
+ * write lived in this session's memory only, so a reload of the
2768
+ * page, a reconnect or a refresh showed no draft while __system__
2769
+ * still differed from the file (N13 of the 2026-09-22 review).
2770
+ * Replaces what the host said before; what this session wrote
2771
+ * stays marked.
2772
+ ***************************************************************/
2773
+ function ac_drafts(gobj, event, kw, src)
2774
+ {
2775
+ let priv = gobj.priv;
2776
+
2777
+ priv.host_drafts = (kw && kw.drafts && typeof kw.drafts === "object")? kw.drafts: {};
2778
+ mark_host_drafts(priv.written, priv.model, priv.host_drafts);
2779
+ if(priv.model) {
2780
+ render(gobj);
2781
+ }
2782
+ return 0;
2783
+ }
2784
+
2758
2785
  /***************************************************************
2759
2786
  * Moving about.
2760
2787
  ***************************************************************/
@@ -3328,7 +3355,8 @@ function create_gclass(gclass_name)
3328
3355
  ["EV_HIDE", ac_hide, null],
3329
3356
  ["EV_TRANSPORT_STATE", ac_transport_state, null],
3330
3357
  ["EV_LANGUAGE_CHANGED", ac_language_changed, null],
3331
- ["EV_REFRESH", ac_refresh, null]
3358
+ ["EV_REFRESH", ac_refresh, null],
3359
+ ["EV_DRAFTS", ac_drafts, null]
3332
3360
  ];
3333
3361
 
3334
3362
  /* What a treedb is open for: reading it whole, and writing it
@@ -3411,6 +3439,7 @@ function create_gclass(gclass_name)
3411
3439
  ["EV_TRANSPORT_STATE", 0],
3412
3440
  ["EV_LANGUAGE_CHANGED", 0],
3413
3441
  ["EV_REFRESH", 0],
3442
+ ["EV_DRAFTS", 0],
3414
3443
 
3415
3444
  /* From this view's own screens. */
3416
3445
  ["EV_SELECT_TREEDB", 0],
@@ -110,6 +110,7 @@ import {
110
110
  import {delete_impact} from "./delete_impact.js";
111
111
 
112
112
  import "./c_yui_treedb_topic_with_form.css";
113
+ import {yui_shell_show_error} from "./shell_modals.js";
113
114
  import {set_toolbar_busy} from "./form_busy.js";
114
115
  import "./tabulator.css";
115
116
  import {cell_text, hook_cell_spec} from "./table_cell_text.js";
@@ -3506,6 +3507,10 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation)
3506
3507
  case "string":
3507
3508
  if(value && is_date(value)) {
3508
3509
  value = value.toISOString();
3510
+ } else if(typeof value === "number" && value > 0) {
3511
+ /* The form hands an epoch in seconds (form_time_value);
3512
+ * a string column stores the instant as ISO text. */
3513
+ value = new Date(value * 1000).toISOString();
3509
3514
  }
3510
3515
  break;
3511
3516
  case "integer":
@@ -4092,6 +4097,9 @@ function ac_confirmed(gobj, event, kw, src)
4092
4097
  * them): never replaced by the row that took their place. */
4093
4098
  log_error(`${gobj_short_name(gobj)}: ${event}: rows gone while ` +
4094
4099
  `confirming the delete, not deleted: ${found.missing.join(", ")}`);
4100
+ /* The person who answered "yes" is told, not only the log
4101
+ * (a low of the 2026-09-22 review). */
4102
+ yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", {t: t});
4095
4103
  }
4096
4104
  if(!found.rows.length) {
4097
4105
  return -1; /* Error already logged, or nothing was named */
@@ -0,0 +1,68 @@
1
+ /***********************************************************************
2
+ * host_drafts.js
3
+ *
4
+ * The topics a schema editor shows as DRAFTS (edited in
5
+ * __system__, not yet saved), told by the host from data. The
6
+ * editor marks a topic when a write of this session lands on
7
+ * it, and that mark lived in the memory of the session only:
8
+ * a reload of the page, a reconnect or a refresh of the model
9
+ * lost the chip, the banner and the export warning while
10
+ * __system__ still differed from the file in use (N13 of the
11
+ * 2026-09-22 review). The host reads that difference from
12
+ * C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
13
+ * hands it here; the editor applies it to its model.
14
+ *
15
+ * Copyright (c) 2026, ArtGins.
16
+ * All Rights Reserved.
17
+ ***********************************************************************/
18
+
19
+ /************************************************************
20
+ * Mark in `written` the topics of `model` the host names as
21
+ * drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
22
+ * topic the model does not hold is ignored. Returns how many were
23
+ * marked.
24
+ ************************************************************/
25
+ function mark_host_drafts(written, model, drafts)
26
+ {
27
+ let marked = 0;
28
+ if(!written || !model || !Array.isArray(model.treedbs) || !drafts) {
29
+ return marked;
30
+ }
31
+ for(let treedb of model.treedbs) {
32
+ const names = drafts[treedb.id];
33
+ if(!Array.isArray(names) || names.length === 0) {
34
+ continue;
35
+ }
36
+ for(let topic of treedb.topics || []) {
37
+ if(names.includes(topic.name)) {
38
+ written[topic.id] = true;
39
+ marked++;
40
+ }
41
+ }
42
+ }
43
+ return marked;
44
+ }
45
+
46
+ /************************************************************
47
+ * What a `saved-schema` answer says, as {treedb_name: [topic names]}:
48
+ * its `data` is one row per treedb (the unnamed form of the command),
49
+ * each carrying `draft_changed` {topic: true}.
50
+ ************************************************************/
51
+ function drafts_of_saved_answer(rows, into)
52
+ {
53
+ const drafts = into || {};
54
+ for(let row of Array.isArray(rows)? rows: []) {
55
+ const treedb_name = row && row.treedb_name;
56
+ const changed = row && row.data && row.data.draft_changed;
57
+ if(!treedb_name || !changed || typeof changed !== "object") {
58
+ continue;
59
+ }
60
+ drafts[treedb_name] = Object.keys(changed).filter((k) => !!changed[k]);
61
+ }
62
+ return drafts;
63
+ }
64
+
65
+ export {
66
+ mark_host_drafts,
67
+ drafts_of_saved_answer,
68
+ };
@@ -0,0 +1,62 @@
1
+ import {describe, it, expect} from "vitest";
2
+ import {mark_host_drafts, drafts_of_saved_answer} from "./host_drafts.js";
3
+
4
+ const model = {
5
+ treedbs: [
6
+ {id: "treedb_a", name: "treedb_a", topics: [
7
+ {id: "treedb_a^users", name: "users"},
8
+ {id: "treedb_a^roles", name: "roles"},
9
+ ]},
10
+ {id: "treedb_b", name: "treedb_b", topics: [
11
+ {id: "treedb_b^users", name: "users"},
12
+ ]},
13
+ ],
14
+ };
15
+
16
+ describe("mark_host_drafts", () => {
17
+
18
+ it("marks the topics the host names, by treedb (N13)", () => {
19
+ /* The mark lived in the memory of one session: after a reload the
20
+ * editor showed no draft while __system__ still differed from the
21
+ * file in use. Rebuilt from the host's data. */
22
+ const written = {};
23
+ const n = mark_host_drafts(written, model, {treedb_a: ["users"]});
24
+ expect(n).toBe(1);
25
+ expect(written).toEqual({"treedb_a^users": true});
26
+ });
27
+
28
+ it("a topic of the same name in another treedb is another topic", () => {
29
+ const written = {};
30
+ mark_host_drafts(written, model, {treedb_b: ["users"]});
31
+ expect(written).toEqual({"treedb_b^users": true});
32
+ });
33
+
34
+ it("keeps what the session wrote, ignores what the model does not hold", () => {
35
+ const written = {"treedb_a^roles": true};
36
+ const n = mark_host_drafts(written, model, {treedb_a: ["nobody"], treedb_z: ["users"]});
37
+ expect(n).toBe(0);
38
+ expect(written).toEqual({"treedb_a^roles": true});
39
+ });
40
+
41
+ it("no model, no drafts: nothing", () => {
42
+ expect(mark_host_drafts({}, null, {treedb_a: ["users"]})).toBe(0);
43
+ expect(mark_host_drafts({}, model, null)).toBe(0);
44
+ });
45
+ });
46
+
47
+ describe("drafts_of_saved_answer", () => {
48
+
49
+ it("reads draft_changed of each treedb row", () => {
50
+ const rows = [
51
+ {treedb_name: "treedb_a", result: 0, data: {draft_changed: {users: true, roles: false}}},
52
+ {treedb_name: "treedb_b", result: 0, data: {draft_changed: {}}},
53
+ {treedb_name: "treedb_c", result: -1, comment: "not open here"},
54
+ ];
55
+ expect(drafts_of_saved_answer(rows)).toEqual({treedb_a: ["users"], treedb_b: []});
56
+ });
57
+
58
+ it("a node older than draft_changed says nothing", () => {
59
+ expect(drafts_of_saved_answer([{treedb_name: "t", data: {can_apply: false}}])).toEqual({});
60
+ expect(drafts_of_saved_answer(null)).toEqual({});
61
+ });
62
+ });