@yuneta/gobj-ui 7.23.42 → 7.23.43

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.
@@ -62151,18 +62151,57 @@ function remote_command(gobj, command, kw) {
62151
62151
  return 0;
62152
62152
  }
62153
62153
  /***************************************************************
62154
- * The three answers are in: build the schema and remember the
62155
- * versions it arrived with. The baseline is what "this topic
62156
- * still needs its version raised" is measured against, so it is
62157
- * taken here and never touched again until the next load.
62154
+ * The three answers are in: build the schema.
62155
+ *
62156
+ * IT DOES NOT TOUCH THE BASELINE, and that is the whole point.
62157
+ * This runs again on the patch after EVERY write, so a baseline
62158
+ * taken here was re-taken as the version the write had just
62159
+ * raised -- and "written, and its version has not moved" then
62160
+ * read TRUE for a topic whose version had just moved.
62161
+ *
62162
+ * The banner that says so offers a Raise button, which wrote
62163
+ * another version, which was measured against itself again: the
62164
+ * banner came back, every time, and could not be dismissed. One
62165
+ * operator pressed it thirty-three times on one column delete.
62166
+ * It is why schema versions climbed by tens with the schema
62167
+ * unchanged.
62168
+ *
62169
+ * See start_measuring(): the baseline belongs to the LOAD, the
62170
+ * same reason `order_undo` was moved out of here before it.
62158
62171
  ***************************************************************/
62159
62172
  function build_model(gobj) {
62160
62173
  let priv = gobj.priv;
62161
62174
  priv.model = build_schema_model(priv.records);
62175
+ }
62176
+ /***************************************************************
62177
+ * From here on, "did this session change it?" has an answer.
62178
+ *
62179
+ * Called ONLY when records arrive from the store: that is what a
62180
+ * measurement of "changed since" can be measured against. Both
62181
+ * halves reset together -- a baseline without its `written` is
62182
+ * the same false positive from the other side, since every
62183
+ * version equals a baseline just taken.
62184
+ ***************************************************************/
62185
+ function start_measuring(gobj) {
62186
+ let priv = gobj.priv;
62162
62187
  priv.baseline = {};
62188
+ priv.written = {};
62189
+ if (!priv.model) return;
62163
62190
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
62164
62191
  }
62165
62192
  /***************************************************************
62193
+ * "This topic was written and its version did not move."
62194
+ *
62195
+ * ONE predicate, because it is asked in two places -- the topic
62196
+ * list draws a chip with it and the column view draws a banner --
62197
+ * and two copies of a rule is how they drift.
62198
+ ***************************************************************/
62199
+ function needs_version_bump(written, baseline, topic) {
62200
+ if (!topic || !written || !baseline) return false;
62201
+ if (!written[topic.id]) return false;
62202
+ return String(baseline[topic.id]) === String(topic.topic_version);
62203
+ }
62204
+ /***************************************************************
62166
62205
  * Say what the whole store would be refused for.
62167
62206
  *
62168
62207
  * Not for this view — it has a button for that — but for
@@ -62602,7 +62641,7 @@ function render_topics(gobj, $body) {
62602
62641
  let twice = repeated_names(treedb.topics);
62603
62642
  let $rows = [];
62604
62643
  for (let topic of treedb.topics) {
62605
- let pending = priv.written[topic.id] && String(priv.baseline[topic.id]) === String(topic.topic_version);
62644
+ let pending = needs_version_bump(priv.written, priv.baseline, topic);
62606
62645
  let ambiguous = !!twice[topic.name];
62607
62646
  let address = ambiguous ? topic.id : topic.name;
62608
62647
  let $actions = [];
@@ -62911,8 +62950,7 @@ function render_columns(gobj, $body) {
62911
62950
  ***************************************************************/
62912
62951
  function version_banner(gobj, topic) {
62913
62952
  let priv = gobj.priv;
62914
- if (!priv.written[topic.id]) return null;
62915
- if (String(priv.baseline[topic.id]) !== String(topic.topic_version)) return null;
62953
+ if (!needs_version_bump(priv.written, priv.baseline, topic)) return null;
62916
62954
  if (is_readonly(gobj)) return null;
62917
62955
  let $banner = (0, _yuneta_gobj_js.createElement2)([
62918
62956
  "div",
@@ -64478,6 +64516,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
64478
64516
  }
64479
64517
  priv.order_undo = {};
64480
64518
  build_model(gobj);
64519
+ start_measuring(gobj);
64481
64520
  return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
64482
64521
  }
64483
64522
  if (command === "update-node" || command === "delete-node") {
@@ -62146,18 +62146,57 @@ function remote_command(gobj, command, kw) {
62146
62146
  return 0;
62147
62147
  }
62148
62148
  /***************************************************************
62149
- * The three answers are in: build the schema and remember the
62150
- * versions it arrived with. The baseline is what "this topic
62151
- * still needs its version raised" is measured against, so it is
62152
- * taken here and never touched again until the next load.
62149
+ * The three answers are in: build the schema.
62150
+ *
62151
+ * IT DOES NOT TOUCH THE BASELINE, and that is the whole point.
62152
+ * This runs again on the patch after EVERY write, so a baseline
62153
+ * taken here was re-taken as the version the write had just
62154
+ * raised -- and "written, and its version has not moved" then
62155
+ * read TRUE for a topic whose version had just moved.
62156
+ *
62157
+ * The banner that says so offers a Raise button, which wrote
62158
+ * another version, which was measured against itself again: the
62159
+ * banner came back, every time, and could not be dismissed. One
62160
+ * operator pressed it thirty-three times on one column delete.
62161
+ * It is why schema versions climbed by tens with the schema
62162
+ * unchanged.
62163
+ *
62164
+ * See start_measuring(): the baseline belongs to the LOAD, the
62165
+ * same reason `order_undo` was moved out of here before it.
62153
62166
  ***************************************************************/
62154
62167
  function build_model(gobj) {
62155
62168
  let priv = gobj.priv;
62156
62169
  priv.model = build_schema_model(priv.records);
62170
+ }
62171
+ /***************************************************************
62172
+ * From here on, "did this session change it?" has an answer.
62173
+ *
62174
+ * Called ONLY when records arrive from the store: that is what a
62175
+ * measurement of "changed since" can be measured against. Both
62176
+ * halves reset together -- a baseline without its `written` is
62177
+ * the same false positive from the other side, since every
62178
+ * version equals a baseline just taken.
62179
+ ***************************************************************/
62180
+ function start_measuring(gobj) {
62181
+ let priv = gobj.priv;
62157
62182
  priv.baseline = {};
62183
+ priv.written = {};
62184
+ if (!priv.model) return;
62158
62185
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
62159
62186
  }
62160
62187
  /***************************************************************
62188
+ * "This topic was written and its version did not move."
62189
+ *
62190
+ * ONE predicate, because it is asked in two places -- the topic
62191
+ * list draws a chip with it and the column view draws a banner --
62192
+ * and two copies of a rule is how they drift.
62193
+ ***************************************************************/
62194
+ function needs_version_bump(written, baseline, topic) {
62195
+ if (!topic || !written || !baseline) return false;
62196
+ if (!written[topic.id]) return false;
62197
+ return String(baseline[topic.id]) === String(topic.topic_version);
62198
+ }
62199
+ /***************************************************************
62161
62200
  * Say what the whole store would be refused for.
62162
62201
  *
62163
62202
  * Not for this view — it has a button for that — but for
@@ -62597,7 +62636,7 @@ function render_topics(gobj, $body) {
62597
62636
  let twice = repeated_names(treedb.topics);
62598
62637
  let $rows = [];
62599
62638
  for (let topic of treedb.topics) {
62600
- let pending = priv.written[topic.id] && String(priv.baseline[topic.id]) === String(topic.topic_version);
62639
+ let pending = needs_version_bump(priv.written, priv.baseline, topic);
62601
62640
  let ambiguous = !!twice[topic.name];
62602
62641
  let address = ambiguous ? topic.id : topic.name;
62603
62642
  let $actions = [];
@@ -62906,8 +62945,7 @@ function render_columns(gobj, $body) {
62906
62945
  ***************************************************************/
62907
62946
  function version_banner(gobj, topic) {
62908
62947
  let priv = gobj.priv;
62909
- if (!priv.written[topic.id]) return null;
62910
- if (String(priv.baseline[topic.id]) !== String(topic.topic_version)) return null;
62948
+ if (!needs_version_bump(priv.written, priv.baseline, topic)) return null;
62911
62949
  if (is_readonly(gobj)) return null;
62912
62950
  let $banner = createElement2([
62913
62951
  "div",
@@ -64473,6 +64511,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
64473
64511
  }
64474
64512
  priv.order_undo = {};
64475
64513
  build_model(gobj);
64514
+ start_measuring(gobj);
64476
64515
  return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
64477
64516
  }
64478
64517
  if (command === "update-node" || command === "delete-node") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.42",
3
+ "version": "7.23.43",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -519,17 +519,50 @@ function remote_command(gobj, command, kw)
519
519
  }
520
520
 
521
521
  /***************************************************************
522
- * The three answers are in: build the schema and remember the
523
- * versions it arrived with. The baseline is what "this topic
524
- * still needs its version raised" is measured against, so it is
525
- * taken here and never touched again until the next load.
522
+ * The three answers are in: build the schema.
523
+ *
524
+ * IT DOES NOT TOUCH THE BASELINE, and that is the whole point.
525
+ * This runs again on the patch after EVERY write, so a baseline
526
+ * taken here was re-taken as the version the write had just
527
+ * raised -- and "written, and its version has not moved" then
528
+ * read TRUE for a topic whose version had just moved.
529
+ *
530
+ * The banner that says so offers a Raise button, which wrote
531
+ * another version, which was measured against itself again: the
532
+ * banner came back, every time, and could not be dismissed. One
533
+ * operator pressed it thirty-three times on one column delete.
534
+ * It is why schema versions climbed by tens with the schema
535
+ * unchanged.
536
+ *
537
+ * See start_measuring(): the baseline belongs to the LOAD, the
538
+ * same reason `order_undo` was moved out of here before it.
526
539
  ***************************************************************/
527
540
  function build_model(gobj)
528
541
  {
529
542
  let priv = gobj.priv;
530
543
 
531
544
  priv.model = build_schema_model(priv.records);
545
+ }
546
+
547
+ /***************************************************************
548
+ * From here on, "did this session change it?" has an answer.
549
+ *
550
+ * Called ONLY when records arrive from the store: that is what a
551
+ * measurement of "changed since" can be measured against. Both
552
+ * halves reset together -- a baseline without its `written` is
553
+ * the same false positive from the other side, since every
554
+ * version equals a baseline just taken.
555
+ ***************************************************************/
556
+ function start_measuring(gobj)
557
+ {
558
+ let priv = gobj.priv;
559
+
532
560
  priv.baseline = {};
561
+ priv.written = {};
562
+
563
+ if(!priv.model) {
564
+ return;
565
+ }
533
566
  for(let treedb of priv.model.treedbs) {
534
567
  for(let topic of treedb.topics) {
535
568
  priv.baseline[topic.id] = topic.topic_version;
@@ -537,6 +570,24 @@ function build_model(gobj)
537
570
  }
538
571
  }
539
572
 
573
+ /***************************************************************
574
+ * "This topic was written and its version did not move."
575
+ *
576
+ * ONE predicate, because it is asked in two places -- the topic
577
+ * list draws a chip with it and the column view draws a banner --
578
+ * and two copies of a rule is how they drift.
579
+ ***************************************************************/
580
+ function needs_version_bump(written, baseline, topic)
581
+ {
582
+ if(!topic || !written || !baseline) {
583
+ return false;
584
+ }
585
+ if(!written[topic.id]) {
586
+ return false;
587
+ }
588
+ return String(baseline[topic.id]) === String(topic.topic_version);
589
+ }
590
+
540
591
  /***************************************************************
541
592
  * Say what the whole store would be refused for.
542
593
  *
@@ -987,8 +1038,7 @@ function render_topics(gobj, $body)
987
1038
  let twice = repeated_names(treedb.topics);
988
1039
  let $rows = [];
989
1040
  for(let topic of treedb.topics) {
990
- let pending = priv.written[topic.id] &&
991
- String(priv.baseline[topic.id]) === String(topic.topic_version);
1041
+ let pending = needs_version_bump(priv.written, priv.baseline, topic);
992
1042
  /* Two topics of one name: the id is what tells them apart, and
993
1043
  * it is what the url has to carry — addressing by name would
994
1044
  * open the first one whichever row was clicked. */
@@ -1187,10 +1237,7 @@ function version_banner(gobj, topic)
1187
1237
  {
1188
1238
  let priv = gobj.priv;
1189
1239
 
1190
- if(!priv.written[topic.id]) {
1191
- return null;
1192
- }
1193
- if(String(priv.baseline[topic.id]) !== String(topic.topic_version)) {
1240
+ if(!needs_version_bump(priv.written, priv.baseline, topic)) {
1194
1241
  return null;
1195
1242
  }
1196
1243
  if(is_readonly(gobj)) {
@@ -2558,6 +2605,11 @@ function ac_mt_command_answer(gobj, event, kw, src)
2558
2605
  * write, which is exactly when the undo has to survive.) */
2559
2606
  priv.order_undo = {};
2560
2607
  build_model(gobj);
2608
+ /* Same reason as order_undo above: this is the LOAD, and the
2609
+ * baseline is what a later "the version did not move" is measured
2610
+ * against. Taken on the write patches instead, it measured every
2611
+ * version against itself. */
2612
+ start_measuring(gobj);
2561
2613
  /* Back to where the operator was: the load may be a refresh, or
2562
2614
  * the answer to a deep link that arrived before the model. */
2563
2615
  return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
@@ -3424,4 +3476,9 @@ function register_c_yui_schema_editor()
3424
3476
  return create_gclass(GCLASS_NAME);
3425
3477
  }
3426
3478
 
3427
- export {register_c_yui_schema_editor};
3479
+ export {
3480
+ register_c_yui_schema_editor,
3481
+ /* Exported for its test: the rule that decides whether a topic still
3482
+ * owes a version bump, which is asked in two places. */
3483
+ needs_version_bump
3484
+ };
@@ -0,0 +1,92 @@
1
+ /***********************************************************************
2
+ * schema_version_bump.test.js
3
+ *
4
+ * "This topic was written and its version did not move."
5
+ *
6
+ * THE RULE MEASURES AGAINST A BASELINE, AND THE BASELINE MUST NOT
7
+ * MOVE WITH IT. It used to be re-taken inside build_model(), which
8
+ * runs again on the patch after EVERY write -- so it came back as
9
+ * the version the write had just raised, the rule read TRUE for a
10
+ * topic whose version HAD moved, and the banner that says so
11
+ * offered a Raise button that wrote another version and was
12
+ * measured against itself again.
13
+ *
14
+ * The banner could not be dismissed. One operator pressed it
15
+ * thirty-three times on a single column delete, and that is why
16
+ * schema versions climbed by tens with the schema unchanged.
17
+ *
18
+ * Copyright (c) 2026, ArtGins.
19
+ * All Rights Reserved.
20
+ ***********************************************************************/
21
+ import {describe, it, expect} from "vitest";
22
+ import {needs_version_bump} from "./c_yui_schema_editor.js";
23
+
24
+ const TOPIC = {id: "device_groups", topic_version: 5};
25
+
26
+ describe("needs_version_bump", () => {
27
+ it("says nothing about a topic this session never wrote", () => {
28
+ expect(needs_version_bump({}, {device_groups: 5}, TOPIC)).toBe(false);
29
+ });
30
+
31
+ it("asks for the bump when the version sits where it was found", () => {
32
+ expect(needs_version_bump({device_groups: true}, {device_groups: 5}, TOPIC))
33
+ .toBe(true);
34
+ });
35
+
36
+ it("STOPS asking once the version has moved past the baseline", () => {
37
+ /* The case the loop got wrong: the write raised it to 6 and the
38
+ * baseline still says 5, so there is nothing left to raise. */
39
+ expect(needs_version_bump(
40
+ {device_groups: true}, {device_groups: 5}, {id: "device_groups", topic_version: 6}
41
+ )).toBe(false);
42
+ });
43
+
44
+ it("compares as text, so 5 and '5' are the same version", () => {
45
+ expect(needs_version_bump(
46
+ {device_groups: true}, {device_groups: "5"}, TOPIC
47
+ )).toBe(true);
48
+ expect(needs_version_bump(
49
+ {device_groups: true}, {device_groups: 5}, {id: "device_groups", topic_version: "5"}
50
+ )).toBe(true);
51
+ });
52
+
53
+ it("answers false rather than throwing on a missing half", () => {
54
+ expect(needs_version_bump(null, {device_groups: 5}, TOPIC)).toBe(false);
55
+ expect(needs_version_bump({device_groups: true}, null, TOPIC)).toBe(false);
56
+ expect(needs_version_bump({device_groups: true}, {device_groups: 5}, null)).toBe(false);
57
+ });
58
+
59
+ it("a topic with no baseline entry is not owed a bump", () => {
60
+ expect(needs_version_bump({device_groups: true}, {}, TOPIC)).toBe(false);
61
+ });
62
+ });
63
+
64
+ describe("the loop it came from", () => {
65
+ /*
66
+ * What the editor does per bump: raise the version by one. With the
67
+ * baseline left alone, ONE press settles it; with the baseline
68
+ * re-taken each time -- the old bug -- it never settles.
69
+ */
70
+ function press_raise(state) {
71
+ return {written: state.written, baseline: state.baseline,
72
+ topic: {id: "t", topic_version: Number(state.topic.topic_version) + 1}};
73
+ }
74
+
75
+ it("settles after a single press", () => {
76
+ let st = {written: {t: true}, baseline: {t: 5}, topic: {id: "t", topic_version: 5}};
77
+ expect(needs_version_bump(st.written, st.baseline, st.topic)).toBe(true);
78
+ st = press_raise(st);
79
+ expect(needs_version_bump(st.written, st.baseline, st.topic)).toBe(false);
80
+ });
81
+
82
+ it("never settles if the baseline is re-taken with the version", () => {
83
+ let st = {written: {t: true}, baseline: {t: 5}, topic: {id: "t", topic_version: 5}};
84
+ let presses = 0;
85
+ while(needs_version_bump(st.written, st.baseline, st.topic) && presses < 40) {
86
+ st = press_raise(st);
87
+ st.baseline = {t: st.topic.topic_version}; /* the bug */
88
+ presses++;
89
+ }
90
+ expect(presses).toBe(40); /* it ran to the guard: no way out */
91
+ });
92
+ });