@yuneta/gobj-ui 7.25.4 → 7.25.6
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 +86 -1
- package/dist/gobj-ui.cjs.js +287 -56
- package/dist/gobj-ui.es.js +288 -57
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +165 -32
- package/src/c_yui_shell.js +61 -8
- package/src/c_yui_treedb_topic_with_form.js +12 -1
- package/src/c_yui_treedb_topics.js +41 -10
- package/src/form_time_value.js +23 -0
- package/src/form_writes_in_flight.js +32 -13
- package/src/form_writes_in_flight.test.js +18 -3
- package/src/host_drafts.js +28 -2
- package/src/host_drafts.test.js +24 -1
- package/src/schema_editor_drafts.wiring.test.js +176 -0
- package/src/schema_editor_transport.wiring.test.js +284 -0
- package/src/schema_version_bump.test.js +10 -5
- package/src/treedb_topics_transport.wiring.test.js +228 -0
- package/src/treedb_write_plan.js +32 -1
- package/src/treedb_write_plan.test.js +22 -1
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -924,6 +924,7 @@ var PRIVATE_DATA$22 = {
|
|
|
924
924
|
avatar_nodes: [],
|
|
925
925
|
translator: null,
|
|
926
926
|
conn_nodes: [],
|
|
927
|
+
connected: null,
|
|
927
928
|
active_dropdown: null
|
|
928
929
|
};
|
|
929
930
|
var __gclass__$22 = null;
|
|
@@ -2555,11 +2556,28 @@ function ac_nav_item_close(gobj, event, kw, src) {
|
|
|
2555
2556
|
/***************************************************************
|
|
2556
2557
|
* FSM
|
|
2557
2558
|
***************************************************************/
|
|
2559
|
+
/***************************************************************
|
|
2560
|
+
* EV_CONNECTION_STATE is an OPTIONAL fact: a view that waits for a
|
|
2561
|
+
* backend answer subscribes to it by name. An app that subscribed
|
|
2562
|
+
* to EVERY event of its shell with one attr (`subscriber`) did not
|
|
2563
|
+
* ask for it and does not declare it -- published to it anyway, the
|
|
2564
|
+
* event would be an FSM error in the app on every connection edge,
|
|
2565
|
+
* and adding it to the shell a breaking change for every such app.
|
|
2566
|
+
* So it reaches only the subscribers that declare it. Every other
|
|
2567
|
+
* event of the shell goes out as before.
|
|
2568
|
+
***************************************************************/
|
|
2569
|
+
function mt_publication_pre_filter(gobj, subs, event, kw) {
|
|
2570
|
+
if (event !== "EV_CONNECTION_STATE") return 1;
|
|
2571
|
+
let subscriber = subs && subs.subscriber;
|
|
2572
|
+
if (!subscriber || !(0, _yuneta_gobj_js.gobj_has_event)(subscriber, event, 0)) return 0;
|
|
2573
|
+
return 1;
|
|
2574
|
+
}
|
|
2558
2575
|
var gmt$22 = {
|
|
2559
2576
|
mt_create: mt_create$22,
|
|
2560
2577
|
mt_start: mt_start$20,
|
|
2561
2578
|
mt_stop: mt_stop$20,
|
|
2562
|
-
mt_destroy: mt_destroy$22
|
|
2579
|
+
mt_destroy: mt_destroy$22,
|
|
2580
|
+
mt_publication_pre_filter
|
|
2563
2581
|
};
|
|
2564
2582
|
function create_gclass$22(gclass_name) {
|
|
2565
2583
|
if (__gclass__$22) {
|
|
@@ -2589,7 +2607,8 @@ function create_gclass$22(gclass_name) {
|
|
|
2589
2607
|
["EV_NAV_ITEM_CLOSE", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_PUBLIC_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
2590
2608
|
["EV_ROUTE_REQUESTED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_PUBLIC_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
2591
2609
|
["EV_ROUTE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_PUBLIC_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
2592
|
-
["EV_LANGUAGE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_PUBLIC_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS]
|
|
2610
|
+
["EV_LANGUAGE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_PUBLIC_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
2611
|
+
["EV_CONNECTION_STATE", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS]
|
|
2593
2612
|
];
|
|
2594
2613
|
__gclass__$22 = (0, _yuneta_gobj_js.gclass_create)(gclass_name, event_types, states, gmt$22, 0, attrs_table$22, PRIVATE_DATA$22, 0, 0, 0, _yuneta_gobj_js.gclass_flag_t.gcflag_no_check_output_events);
|
|
2595
2614
|
if (!__gclass__$22) return -1;
|
|
@@ -2872,13 +2891,17 @@ function yui_shell_translate(shell_gobj, $el) {
|
|
|
2872
2891
|
* is-connected / is-disconnected classes; CSS owns the look.
|
|
2873
2892
|
************************************************************/
|
|
2874
2893
|
function yui_shell_set_connection_state(shell_gobj, connected) {
|
|
2894
|
+
if (!shell_gobj || !(0, _yuneta_gobj_js.is_gobj)(shell_gobj) || (0, _yuneta_gobj_js.gobj_is_destroying)(shell_gobj)) return;
|
|
2875
2895
|
let priv = shell_gobj.priv;
|
|
2876
|
-
if (!priv
|
|
2896
|
+
if (!priv) return;
|
|
2877
2897
|
let on = !!connected;
|
|
2878
|
-
|
|
2898
|
+
let changed = priv.connected !== on;
|
|
2899
|
+
priv.connected = on;
|
|
2900
|
+
if ((0, _yuneta_gobj_js.is_array)(priv.conn_nodes)) for (let $n of priv.conn_nodes) {
|
|
2879
2901
|
$n.classList.toggle("is-connected", on);
|
|
2880
2902
|
$n.classList.toggle("is-disconnected", !on);
|
|
2881
2903
|
}
|
|
2904
|
+
if (changed) (0, _yuneta_gobj_js.gobj_publish_event)(shell_gobj, "EV_CONNECTION_STATE", { connected: on });
|
|
2882
2905
|
}
|
|
2883
2906
|
/************************************************************
|
|
2884
2907
|
* Swap the icon of a toolbar item at runtime (host-driven,
|
|
@@ -7319,6 +7342,29 @@ function json_diff_rows(doc_a, doc_b) {
|
|
|
7319
7342
|
* them, and they must be each other's inverse: a writable time
|
|
7320
7343
|
* column travels back on EVERY save of the record.
|
|
7321
7344
|
*
|
|
7345
|
+
* KNOWN LIMIT -- the change of hour. A `datetime-local` holds a
|
|
7346
|
+
* LOCAL WALL time and no offset, and a wall time is not always
|
|
7347
|
+
* one instant:
|
|
7348
|
+
*
|
|
7349
|
+
* - the night the clocks go back (autumn), one hour happens
|
|
7350
|
+
* twice. "02:30" names two instants an hour apart, and
|
|
7351
|
+
* `new Date("YYYY-MM-DDT02:30:00")` answers the EARLIER one
|
|
7352
|
+
* (ECMAScript's "compatible" disambiguation). An epoch in the
|
|
7353
|
+
* SECOND pass of that hour is shown as 02:30 and comes back
|
|
7354
|
+
* one hour earlier: saving the record -- any field of it --
|
|
7355
|
+
* moves a writable time column that holds it by -3600 s.
|
|
7356
|
+
* - the night the clocks go forward (spring), one hour does not
|
|
7357
|
+
* exist. No epoch is shown in it, so nothing read comes back
|
|
7358
|
+
* wrong; a wall time TYPED in it is moved forward by the
|
|
7359
|
+
* browser.
|
|
7360
|
+
*
|
|
7361
|
+
* Not fixable here without the offset, which the input cannot
|
|
7362
|
+
* carry. What is safe: a read-only time column never goes back
|
|
7363
|
+
* (treedb_write_plan.js, col_goes_back_to_treedb), and a time
|
|
7364
|
+
* pkey2 goes back as the record had it, never through this
|
|
7365
|
+
* widget (instance_keys_of). A writable time column edited or
|
|
7366
|
+
* merely saved inside the repeated hour is the exposed case.
|
|
7367
|
+
*
|
|
7322
7368
|
* Copyright (c) 2026, ArtGins.
|
|
7323
7369
|
* All Rights Reserved.
|
|
7324
7370
|
****************************************************************************/
|
|
@@ -23026,30 +23072,46 @@ function yui_shell_show_route_map(shell, opts) {
|
|
|
23026
23072
|
* form_writes_in_flight.js
|
|
23027
23073
|
*
|
|
23028
23074
|
* The writes of topic forms a C_YUI_TREEDB_TOPICS view has sent
|
|
23029
|
-
* and not yet answered
|
|
23030
|
-
*
|
|
23031
|
-
*
|
|
23032
|
-
*
|
|
23033
|
-
*
|
|
23034
|
-
*
|
|
23035
|
-
*
|
|
23075
|
+
* and not yet answered. A form that waits for its answer
|
|
23076
|
+
* (form_waits_for_answer) stays open and busy until the host
|
|
23077
|
+
* answers, so the host has to answer EVERY way a write ends --
|
|
23078
|
+
* and the transport closing under a write in flight, or a
|
|
23079
|
+
* command that never left because there was no session, are two
|
|
23080
|
+
* of them that nobody answered: the form stayed busy for ever
|
|
23081
|
+
* (N8 of the 2026-09-22 review).
|
|
23082
|
+
*
|
|
23083
|
+
* A write is known by its TOPIC and its `form_write` serial,
|
|
23084
|
+
* never by the serial alone: each topic has its own form, and
|
|
23085
|
+
* each form counts its serials from 1. Keyed by the serial, two
|
|
23086
|
+
* forms with a write in flight at once shared the key "1", the
|
|
23087
|
+
* first answer settled both, and the transport closing then
|
|
23088
|
+
* answered only one of them.
|
|
23036
23089
|
*
|
|
23037
23090
|
* Copyright (c) 2026, ArtGins.
|
|
23038
23091
|
* All Rights Reserved.
|
|
23039
23092
|
***********************************************************************/
|
|
23040
23093
|
/************************************************************
|
|
23094
|
+
* The key of one write: `<topic>^<serial>`.
|
|
23095
|
+
************************************************************/
|
|
23096
|
+
function form_write_key(topic_name, form_write) {
|
|
23097
|
+
return `${topic_name || ""}^${form_write}`;
|
|
23098
|
+
}
|
|
23099
|
+
/************************************************************
|
|
23041
23100
|
* A write left: remember whose it is
|
|
23042
23101
|
************************************************************/
|
|
23043
23102
|
function track_form_write(in_flight, form_write, topic_name) {
|
|
23044
23103
|
if (!in_flight || !form_write) return;
|
|
23045
|
-
in_flight[form_write] =
|
|
23104
|
+
in_flight[form_write_key(topic_name, form_write)] = {
|
|
23105
|
+
form_write,
|
|
23106
|
+
topic_name: topic_name || ""
|
|
23107
|
+
};
|
|
23046
23108
|
}
|
|
23047
23109
|
/************************************************************
|
|
23048
23110
|
* A write was answered, one way or the other
|
|
23049
23111
|
************************************************************/
|
|
23050
|
-
function settle_form_write(in_flight, form_write) {
|
|
23112
|
+
function settle_form_write(in_flight, form_write, topic_name) {
|
|
23051
23113
|
if (!in_flight || !form_write) return;
|
|
23052
|
-
delete in_flight[form_write];
|
|
23114
|
+
delete in_flight[form_write_key(topic_name, form_write)];
|
|
23053
23115
|
}
|
|
23054
23116
|
/************************************************************
|
|
23055
23117
|
* Nothing in flight can be answered any more: take them all,
|
|
@@ -23058,12 +23120,13 @@ function settle_form_write(in_flight, form_write) {
|
|
|
23058
23120
|
function abandon_form_writes(in_flight) {
|
|
23059
23121
|
const abandoned = [];
|
|
23060
23122
|
if (!in_flight) return abandoned;
|
|
23061
|
-
Object.keys(in_flight).forEach((
|
|
23123
|
+
Object.keys(in_flight).forEach((key) => {
|
|
23124
|
+
const w = in_flight[key];
|
|
23062
23125
|
abandoned.push({
|
|
23063
|
-
form_write: Number(
|
|
23064
|
-
topic_name:
|
|
23126
|
+
form_write: Number(w.form_write),
|
|
23127
|
+
topic_name: w.topic_name
|
|
23065
23128
|
});
|
|
23066
|
-
delete in_flight[
|
|
23129
|
+
delete in_flight[key];
|
|
23067
23130
|
});
|
|
23068
23131
|
return abandoned;
|
|
23069
23132
|
}
|
|
@@ -23635,6 +23698,7 @@ var __gclass__$6 = null;
|
|
|
23635
23698
|
***************************************************************/
|
|
23636
23699
|
function mt_create$6(gobj) {
|
|
23637
23700
|
gobj.priv.form_writes = {};
|
|
23701
|
+
gobj.priv.conn_shell = null;
|
|
23638
23702
|
build_ui$6(gobj);
|
|
23639
23703
|
let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
|
|
23640
23704
|
if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
|
|
@@ -23644,12 +23708,22 @@ function mt_create$6(gobj) {
|
|
|
23644
23708
|
* Framework Method: Start
|
|
23645
23709
|
***************************************************************/
|
|
23646
23710
|
function mt_start$6(gobj) {
|
|
23711
|
+
let shell = yui_shell_of(gobj);
|
|
23712
|
+
if (shell) {
|
|
23713
|
+
(0, _yuneta_gobj_js.gobj_subscribe_event)(shell, "EV_CONNECTION_STATE", {}, gobj);
|
|
23714
|
+
gobj.priv.conn_shell = shell;
|
|
23715
|
+
}
|
|
23647
23716
|
request_treedb_descs$1(gobj);
|
|
23648
23717
|
}
|
|
23649
23718
|
/***************************************************************
|
|
23650
23719
|
* Framework Method: Stop
|
|
23651
23720
|
***************************************************************/
|
|
23652
23721
|
function mt_stop$6(gobj) {
|
|
23722
|
+
let priv = gobj.priv;
|
|
23723
|
+
if (priv.conn_shell) {
|
|
23724
|
+
if (!(0, _yuneta_gobj_js.gobj_is_destroying)(priv.conn_shell)) (0, _yuneta_gobj_js.gobj_unsubscribe_event)(priv.conn_shell, "EV_CONNECTION_STATE", {}, gobj);
|
|
23725
|
+
priv.conn_shell = null;
|
|
23726
|
+
}
|
|
23653
23727
|
let shell = yui_shell_of(gobj);
|
|
23654
23728
|
if (shell) yui_shell_set_sub_routes(shell, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "base_route"), null);
|
|
23655
23729
|
close_json_viewer$1(gobj);
|
|
@@ -25055,7 +25129,7 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
25055
25129
|
if (command === "update-node" || command === "create-node") {
|
|
25056
25130
|
let failed_topic = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0);
|
|
25057
25131
|
if (failed_topic) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name: failed_topic }, gobj);
|
|
25058
|
-
settle_form_write(gobj.priv.form_writes, (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0));
|
|
25132
|
+
settle_form_write(gobj.priv.form_writes, (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), failed_topic);
|
|
25059
25133
|
answer_form_write(gobj, get_gobj_formtable(gobj, failed_topic), (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), false);
|
|
25060
25134
|
}
|
|
25061
25135
|
}
|
|
@@ -25093,7 +25167,7 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
25093
25167
|
created: command === "create-node",
|
|
25094
25168
|
command
|
|
25095
25169
|
});
|
|
25096
|
-
settle_form_write(gobj.priv.form_writes, (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0));
|
|
25170
|
+
settle_form_write(gobj.priv.form_writes, (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0));
|
|
25097
25171
|
answer_form_write(gobj, get_gobj_formtable(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0)), (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), true);
|
|
25098
25172
|
break;
|
|
25099
25173
|
case "delete-node": break;
|
|
@@ -25237,11 +25311,19 @@ function ac_back_to_topics(gobj, event, kw, src) {
|
|
|
25237
25311
|
return 0;
|
|
25238
25312
|
}
|
|
25239
25313
|
/************************************************************
|
|
25240
|
-
*
|
|
25241
|
-
*
|
|
25242
|
-
*
|
|
25243
|
-
*
|
|
25244
|
-
*
|
|
25314
|
+
* A backend transport edge, from either of two places:
|
|
25315
|
+
* - EV_TRANSPORT_STATE, forwarded by a host that keeps one
|
|
25316
|
+
* transport per view (gui_treedb's C_TREEDB_VIEW, gui_agent's
|
|
25317
|
+
* C_AGENT_TREEDB_VIEW);
|
|
25318
|
+
* - EV_CONNECTION_STATE, published by the SHELL when the app tells
|
|
25319
|
+
* it its backend went up or down (yui_shell_set_connection_state),
|
|
25320
|
+
* which is what every single-backend app already does for the
|
|
25321
|
+
* toolbar dot. No host has to remember anything for this one.
|
|
25322
|
+
* Both can arrive for one edge; what is done here is idempotent.
|
|
25323
|
+
* The toolbar disables the JSON viewers the moment the session drops
|
|
25324
|
+
* (and re-enables them on reconnect). The library view must not
|
|
25325
|
+
* subscribe to the C_IEVENT_CLI itself: that forwards the subscription
|
|
25326
|
+
* upstream and breaks the session.
|
|
25245
25327
|
************************************************************/
|
|
25246
25328
|
function ac_transport_state$2(gobj, event, kw, src) {
|
|
25247
25329
|
let connected = !!(kw && kw.connected);
|
|
@@ -25773,6 +25855,11 @@ function create_gclass$6(gclass_name) {
|
|
|
25773
25855
|
"EV_TRANSPORT_STATE",
|
|
25774
25856
|
ac_transport_state$2,
|
|
25775
25857
|
null
|
|
25858
|
+
],
|
|
25859
|
+
[
|
|
25860
|
+
"EV_CONNECTION_STATE",
|
|
25861
|
+
ac_transport_state$2,
|
|
25862
|
+
null
|
|
25776
25863
|
]
|
|
25777
25864
|
]]];
|
|
25778
25865
|
const event_types = [
|
|
@@ -25803,6 +25890,7 @@ function create_gclass$6(gclass_name) {
|
|
|
25803
25890
|
["EV_HIDE", 0],
|
|
25804
25891
|
["EV_BACK_TO_TOPICS", 0],
|
|
25805
25892
|
["EV_TRANSPORT_STATE", 0],
|
|
25893
|
+
["EV_CONNECTION_STATE", 0],
|
|
25806
25894
|
["EV_TOPIC_SELECTED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS]
|
|
25807
25895
|
];
|
|
25808
25896
|
__gclass__$6 = (0, _yuneta_gobj_js.gclass_create)(gclass_name, event_types, states, gmt$6, 0, attrs_table$6, PRIVATE_DATA$6, 0, 0, 0, 0);
|
|
@@ -26520,6 +26608,23 @@ function col_goes_back_to_treedb(desc, col) {
|
|
|
26520
26608
|
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
26521
26609
|
return topic_pkey2s(d).includes(col.id);
|
|
26522
26610
|
}
|
|
26611
|
+
/************************************************************
|
|
26612
|
+
* The INSTANCE an update of `record` is for: its pkey2 values, as the
|
|
26613
|
+
* record held them when the form was opened, {pkey2: value}.
|
|
26614
|
+
*
|
|
26615
|
+
* They go back as they WERE, never as the form's widget hands them: a
|
|
26616
|
+
* `time` pkey2 is an epoch rendered in a `datetime-local`, and a value
|
|
26617
|
+
* that crossed the widget (seconds dropped, a local wall time read
|
|
26618
|
+
* back, an hour the autumn change names twice) names ANOTHER instance
|
|
26619
|
+
* -- or none, and C_NODE falls back to the primary. The pkey2 is an
|
|
26620
|
+
* address, not a field of the edit.
|
|
26621
|
+
************************************************************/
|
|
26622
|
+
function instance_keys_of(record, desc) {
|
|
26623
|
+
const keys = {};
|
|
26624
|
+
if (!record || typeof record !== "object") return keys;
|
|
26625
|
+
for (const k of topic_pkey2s(desc || {})) if (Object.prototype.hasOwnProperty.call(record, k) && record[k] !== void 0 && record[k] !== null) keys[k] = record[k];
|
|
26626
|
+
return keys;
|
|
26627
|
+
}
|
|
26523
26628
|
//#endregion
|
|
26524
26629
|
//#region src/delete_impact.js
|
|
26525
26630
|
/***********************************************************************
|
|
@@ -28251,6 +28356,7 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
28251
28356
|
},
|
|
28252
28357
|
[]
|
|
28253
28358
|
]);
|
|
28359
|
+
priv.form_instance = form_mode === "update" ? instance_keys_of(record, desc) : null;
|
|
28254
28360
|
let form_plan = write_plan(gobj);
|
|
28255
28361
|
let form = (0, _yuneta_gobj_js.gobj_create_pure_child)("form_" + (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj)), "C_YUI_FORM", {
|
|
28256
28362
|
template: build_form_template(gobj),
|
|
@@ -28319,6 +28425,7 @@ function teardown_form_child(gobj) {
|
|
|
28319
28425
|
priv.reading_files = 0;
|
|
28320
28426
|
priv.awaiting_write = 0;
|
|
28321
28427
|
priv.form_busy = { busy: false };
|
|
28428
|
+
priv.form_instance = null;
|
|
28322
28429
|
if (priv.form) {
|
|
28323
28430
|
if ((0, _yuneta_gobj_js.gobj_is_running)(priv.form)) (0, _yuneta_gobj_js.gobj_stop)(priv.form);
|
|
28324
28431
|
(0, _yuneta_gobj_js.gobj_destroy)(priv.form);
|
|
@@ -29578,6 +29685,7 @@ function publish_treedb_write(gobj, mode, record, jn_files, form_write) {
|
|
|
29578
29685
|
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
29579
29686
|
record: strip_read_only_cols(gobj, record)
|
|
29580
29687
|
};
|
|
29688
|
+
if (mode !== "create" && gobj.priv.form_instance) Object.assign(kw_write.record, gobj.priv.form_instance);
|
|
29581
29689
|
if (form_write) kw_write.form_write = form_write;
|
|
29582
29690
|
if (jn_files && Object.keys(jn_files).length > 0) kw_write.record.__files__ = jn_files;
|
|
29583
29691
|
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", kw_write);
|
|
@@ -70278,16 +70386,39 @@ function register_c_yui_treedb_schema() {
|
|
|
70278
70386
|
* __system__ still differed from the file in use (N13 of the
|
|
70279
70387
|
* 2026-09-22 review). The host reads that difference from
|
|
70280
70388
|
* C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
70281
|
-
* hands it here; the editor applies it to its model
|
|
70389
|
+
* hands it here; the editor applies it to its model, in place
|
|
70390
|
+
* of what the host said before.
|
|
70391
|
+
*
|
|
70392
|
+
* What `draft_changed` measures depends on the node: the draft
|
|
70393
|
+
* against the SAVED schema on a node newer than yunetas 7.25.3,
|
|
70394
|
+
* and against the file IN USE on a 7.25.3 node -- where a topic saved and
|
|
70395
|
+
* not yet applied is still named, and the editor, which cannot
|
|
70396
|
+
* tell the two apart, shows it as a draft until the Apply.
|
|
70282
70397
|
*
|
|
70283
70398
|
* Copyright (c) 2026, ArtGins.
|
|
70284
70399
|
* All Rights Reserved.
|
|
70285
70400
|
***********************************************************************/
|
|
70286
70401
|
/************************************************************
|
|
70402
|
+
* The topics of `model` the host names as drafts, as
|
|
70403
|
+
* {topic id: true}: `drafts` is {treedb_name: [topic names]}. A
|
|
70404
|
+
* treedb or a topic the model does not hold is ignored.
|
|
70405
|
+
*
|
|
70406
|
+
* What the editor keeps is THIS, apart from what its own session
|
|
70407
|
+
* wrote, and replaced whole on every EV_DRAFTS: the host is the
|
|
70408
|
+
* truth after a reload. Folded into the session's marks, a topic the
|
|
70409
|
+
* host once named stayed a draft after the save that published it.
|
|
70410
|
+
************************************************************/
|
|
70411
|
+
function host_draft_ids(model, drafts) {
|
|
70412
|
+
const ids = {};
|
|
70413
|
+
mark_host_drafts(ids, model, drafts);
|
|
70414
|
+
return ids;
|
|
70415
|
+
}
|
|
70416
|
+
/************************************************************
|
|
70287
70417
|
* Mark in `written` the topics of `model` the host names as
|
|
70288
70418
|
* drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
|
|
70289
70419
|
* topic the model does not hold is ignored. Returns how many were
|
|
70290
|
-
* marked.
|
|
70420
|
+
* marked. It only ADDS: a caller that must forget what the host
|
|
70421
|
+
* no longer names starts from an empty map (host_draft_ids()).
|
|
70291
70422
|
************************************************************/
|
|
70292
70423
|
function mark_host_drafts(written, model, drafts) {
|
|
70293
70424
|
let marked = 0;
|
|
@@ -71926,8 +72057,9 @@ function write_options(op) {
|
|
|
71926
72057
|
* the owner's design of M36 (2026-09-21 review) every write here
|
|
71927
72058
|
* raised both versions, so an edit half made was already the
|
|
71928
72059
|
* schema of the next start. The topic list marks what this session
|
|
71929
|
-
* wrote and has not saved
|
|
71930
|
-
* what forgets it
|
|
72060
|
+
* wrote and has not saved (the host's EV_REFRESH after a save is
|
|
72061
|
+
* what forgets it) and what the host says is not saved (EV_DRAFTS,
|
|
72062
|
+
* replaced whole each time it is sent).
|
|
71931
72063
|
*
|
|
71932
72064
|
* WHAT ELSE IS HERE, and why each is here and not somewhere else:
|
|
71933
72065
|
*
|
|
@@ -71969,6 +72101,12 @@ function write_options(op) {
|
|
|
71969
72101
|
* `update-node` / `delete-node` on the treedb service, and it may
|
|
71970
72102
|
* be a routing adapter two hops away (the agent console's
|
|
71971
72103
|
* C_AGENT_TREEDB_LINK) — this view cannot tell, and must not.
|
|
72104
|
+
* A DROP of it (the host's EV_TRANSPORT_STATE, or a failure that
|
|
72105
|
+
* arrives while the transport is out of session) ends the load or
|
|
72106
|
+
* the write it cut, and the model is asked again when the session
|
|
72107
|
+
* is back. Every request carries its round (`round` for a load,
|
|
72108
|
+
* `write` for a write) in `__md_command__`, so an answer of what
|
|
72109
|
+
* was cut is ignored instead of counted in the next one.
|
|
71972
72110
|
*
|
|
71973
72111
|
* Copyright (c) 2026, ArtGins.
|
|
71974
72112
|
* All Rights Reserved.
|
|
@@ -72001,18 +72139,23 @@ var PRIVATE_DATA$1 = {
|
|
|
72001
72139
|
model: null,
|
|
72002
72140
|
pending: 0,
|
|
72003
72141
|
load_error: "",
|
|
72142
|
+
load_round: 0,
|
|
72004
72143
|
treedb_id: "",
|
|
72005
72144
|
topic_name: "",
|
|
72006
72145
|
diagram: false,
|
|
72007
72146
|
baseline: null,
|
|
72008
72147
|
written: null,
|
|
72148
|
+
host_drafts: null,
|
|
72149
|
+
host_ids: null,
|
|
72009
72150
|
order_undo: null,
|
|
72010
72151
|
diagram_gobj: null,
|
|
72011
72152
|
dialog: null,
|
|
72012
72153
|
save_queue: null,
|
|
72013
72154
|
save_return: "",
|
|
72014
72155
|
save_wrote: false,
|
|
72156
|
+
write_tag: 0,
|
|
72015
72157
|
connected: false,
|
|
72158
|
+
reload_on_open: false,
|
|
72016
72159
|
pending_seg: null,
|
|
72017
72160
|
import_plan: null,
|
|
72018
72161
|
import_pane: null,
|
|
@@ -72030,6 +72173,7 @@ function mt_create$1(gobj) {
|
|
|
72030
72173
|
priv.baseline = {};
|
|
72031
72174
|
priv.written = {};
|
|
72032
72175
|
priv.host_drafts = {};
|
|
72176
|
+
priv.host_ids = {};
|
|
72033
72177
|
priv.save_queue = [];
|
|
72034
72178
|
build_ui$1(gobj);
|
|
72035
72179
|
let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
|
|
@@ -72182,6 +72326,7 @@ function request_model(gobj) {
|
|
|
72182
72326
|
};
|
|
72183
72327
|
priv.load_error = "";
|
|
72184
72328
|
priv.pending = 0;
|
|
72329
|
+
priv.load_round++;
|
|
72185
72330
|
(0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_LOADING");
|
|
72186
72331
|
show_notice(gobj, "loading the schemas");
|
|
72187
72332
|
for (let topic_name of [
|
|
@@ -72191,7 +72336,7 @@ function request_model(gobj) {
|
|
|
72191
72336
|
]) if (remote_command(gobj, "nodes", {
|
|
72192
72337
|
topic_name,
|
|
72193
72338
|
options: { list_dict: true }
|
|
72194
|
-
}) === 0) priv.pending++;
|
|
72339
|
+
}, { round: priv.load_round }) === 0) priv.pending++;
|
|
72195
72340
|
if (priv.pending === 0) {
|
|
72196
72341
|
priv.load_error = (0, i18next.t)("cannot reach the treedb");
|
|
72197
72342
|
(0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_IDLE");
|
|
@@ -72209,7 +72354,7 @@ function request_model(gobj) {
|
|
|
72209
72354
|
* answer says which topic it is about, since the command is
|
|
72210
72355
|
* `nodes` for all three.
|
|
72211
72356
|
***************************************************************/
|
|
72212
|
-
function remote_command(gobj, command, kw) {
|
|
72357
|
+
function remote_command(gobj, command, kw, tag) {
|
|
72213
72358
|
let remote = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno");
|
|
72214
72359
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
72215
72360
|
if (!remote) {
|
|
@@ -72220,11 +72365,11 @@ function remote_command(gobj, command, kw) {
|
|
|
72220
72365
|
service: treedb_name,
|
|
72221
72366
|
treedb_name
|
|
72222
72367
|
}, kw || {});
|
|
72223
|
-
full_kw.__md_command__ = {
|
|
72368
|
+
full_kw.__md_command__ = Object.assign({
|
|
72224
72369
|
purpose: PURPOSE,
|
|
72225
72370
|
topic_name: kw && kw.topic_name || "",
|
|
72226
72371
|
record_id: kw && kw.record_id || ""
|
|
72227
|
-
};
|
|
72372
|
+
}, tag || {});
|
|
72228
72373
|
delete full_kw.record_id;
|
|
72229
72374
|
let ret = (0, _yuneta_gobj_js.gobj_command)(remote, command, full_kw, gobj);
|
|
72230
72375
|
if (ret) {
|
|
@@ -72269,25 +72414,33 @@ function start_measuring(gobj) {
|
|
|
72269
72414
|
let priv = gobj.priv;
|
|
72270
72415
|
priv.baseline = {};
|
|
72271
72416
|
priv.written = {};
|
|
72417
|
+
priv.host_ids = {};
|
|
72272
72418
|
if (!priv.model) return;
|
|
72273
72419
|
for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
|
|
72274
|
-
|
|
72420
|
+
priv.host_ids = host_draft_ids(priv.model, priv.host_drafts);
|
|
72275
72421
|
}
|
|
72276
72422
|
/***************************************************************
|
|
72277
|
-
* "This topic
|
|
72423
|
+
* "This topic holds an edit that is not saved."
|
|
72278
72424
|
*
|
|
72279
72425
|
* An edit is a DRAFT: it moves no version and reaches no treedb
|
|
72280
72426
|
* until the host saves it (C_TREEDB's save-schema, which raises the
|
|
72281
|
-
* versions of what changed)
|
|
72282
|
-
*
|
|
72427
|
+
* versions of what changed). Two sources say a topic is one, and
|
|
72428
|
+
* they are kept APART because they are forgotten differently:
|
|
72429
|
+
* - `written`: what THIS session wrote since the model was loaded
|
|
72430
|
+
* (a reload, the host's EV_REFRESH, forgets it -- the store says
|
|
72431
|
+
* what is left);
|
|
72432
|
+
* - `host_ids`: what the host said last (EV_DRAFTS), replaced WHOLE
|
|
72433
|
+
* by the next EV_DRAFTS. Folded into `written`, a topic the host
|
|
72434
|
+
* named once stayed a draft after the Save that published it.
|
|
72283
72435
|
*
|
|
72284
72436
|
* ONE predicate, because it is asked in three places -- the topic
|
|
72285
72437
|
* list draws a chip with it, the column view a banner, the export
|
|
72286
72438
|
* a warning -- and copies of a rule are how they drift.
|
|
72287
72439
|
***************************************************************/
|
|
72288
|
-
function topic_is_draft(
|
|
72289
|
-
if (!topic || !
|
|
72290
|
-
|
|
72440
|
+
function topic_is_draft(priv, topic) {
|
|
72441
|
+
if (!topic || !priv) return false;
|
|
72442
|
+
if (priv.written && priv.written[topic.id]) return true;
|
|
72443
|
+
return priv.host_ids && priv.host_ids[topic.id] ? true : false;
|
|
72291
72444
|
}
|
|
72292
72445
|
/***************************************************************
|
|
72293
72446
|
* Say what the whole store would be refused for.
|
|
@@ -72729,7 +72882,7 @@ function render_topics(gobj, $body) {
|
|
|
72729
72882
|
let twice = repeated_names(treedb.topics);
|
|
72730
72883
|
let $rows = [];
|
|
72731
72884
|
for (let topic of treedb.topics) {
|
|
72732
|
-
let pending = topic_is_draft(priv
|
|
72885
|
+
let pending = topic_is_draft(priv, topic);
|
|
72733
72886
|
let ambiguous = !!twice[topic.name];
|
|
72734
72887
|
let address = ambiguous ? topic.id : topic.name;
|
|
72735
72888
|
let $actions = [];
|
|
@@ -73052,7 +73205,7 @@ function render_columns(gobj, $body) {
|
|
|
73052
73205
|
***************************************************************/
|
|
73053
73206
|
function draft_banner(gobj, topic) {
|
|
73054
73207
|
let priv = gobj.priv;
|
|
73055
|
-
if (!topic_is_draft(priv
|
|
73208
|
+
if (!topic_is_draft(priv, topic)) return null;
|
|
73056
73209
|
if (is_readonly(gobj)) return null;
|
|
73057
73210
|
return (0, _yuneta_gobj_js.createElement2)([
|
|
73058
73211
|
"div",
|
|
@@ -73942,7 +74095,7 @@ function open_report(gobj, findings, treedb_id) {
|
|
|
73942
74095
|
***************************************************************/
|
|
73943
74096
|
function open_export(gobj, treedb) {
|
|
73944
74097
|
let priv = gobj.priv;
|
|
73945
|
-
let drafts = treedb.topics.some((topic) => topic_is_draft(priv
|
|
74098
|
+
let drafts = treedb.topics.some((topic) => topic_is_draft(priv, topic));
|
|
73946
74099
|
let c_text = schema_to_c(treedb);
|
|
73947
74100
|
let json_text = JSON.stringify(schema_to_json(treedb), null, 4);
|
|
73948
74101
|
let $content = (0, _yuneta_gobj_js.createElement2)([
|
|
@@ -74447,12 +74600,61 @@ function run_next_write(gobj) {
|
|
|
74447
74600
|
let write = priv.save_queue[0];
|
|
74448
74601
|
let command = write_command(write.op);
|
|
74449
74602
|
let options = write_options(write.op);
|
|
74603
|
+
priv.write_tag++;
|
|
74450
74604
|
if (remote_command(gobj, command, {
|
|
74451
74605
|
topic_name: write.topic_name,
|
|
74452
74606
|
record: write.record,
|
|
74453
74607
|
options,
|
|
74454
74608
|
record_id: write.record.id || ""
|
|
74455
|
-
}) < 0) return end_writes(gobj, (0, i18next.t)("cannot reach the treedb"));
|
|
74609
|
+
}, { write: priv.write_tag }) < 0) return end_writes(gobj, (0, i18next.t)("cannot reach the treedb"));
|
|
74610
|
+
return 0;
|
|
74611
|
+
}
|
|
74612
|
+
/***************************************************************
|
|
74613
|
+
* Does the transport say it is in session? The state the library
|
|
74614
|
+
* reads off every transport (C_IEVENT_CLI and the routing adapters
|
|
74615
|
+
* name it the same).
|
|
74616
|
+
***************************************************************/
|
|
74617
|
+
function transport_in_session(gobj) {
|
|
74618
|
+
let remote = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno");
|
|
74619
|
+
return !!remote && (0, _yuneta_gobj_js.gobj_current_state)(remote) === "ST_SESSION";
|
|
74620
|
+
}
|
|
74621
|
+
/***************************************************************
|
|
74622
|
+
* Is this answer of the load in flight, or of the write in
|
|
74623
|
+
* flight? Each request carries its round in `__md_command__`, and
|
|
74624
|
+
* one the drop abandoned (or a deadline settled) can still come
|
|
74625
|
+
* back: counted, it ended the next load with the old data or
|
|
74626
|
+
* moved the next write's queue.
|
|
74627
|
+
***************************************************************/
|
|
74628
|
+
function is_current_load(gobj, md) {
|
|
74629
|
+
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;
|
|
74630
|
+
}
|
|
74631
|
+
function is_current_write(gobj, md) {
|
|
74632
|
+
return (0, _yuneta_gobj_js.gobj_current_state)(gobj) === "ST_SAVING" && (0, _yuneta_gobj_js.kw_get_int)(gobj, md, "write", 0, 0) === gobj.priv.write_tag;
|
|
74633
|
+
}
|
|
74634
|
+
/***************************************************************
|
|
74635
|
+
* The transport dropped under a load or a write: its answers
|
|
74636
|
+
* will never come. The load goes back to the screen it replaced
|
|
74637
|
+
* (or to "not connected" when there was none), the write ends
|
|
74638
|
+
* with the drop said -- whether it landed is unknown -- and both
|
|
74639
|
+
* mark the model to be asked again when the session is back.
|
|
74640
|
+
* Bumping the round makes an answer of what was cut stale.
|
|
74641
|
+
***************************************************************/
|
|
74642
|
+
function transport_dropped(gobj) {
|
|
74643
|
+
let priv = gobj.priv;
|
|
74644
|
+
let state = (0, _yuneta_gobj_js.gobj_current_state)(gobj);
|
|
74645
|
+
priv.reload_on_open = true;
|
|
74646
|
+
if (state === "ST_SAVING") {
|
|
74647
|
+
priv.write_tag++;
|
|
74648
|
+
return end_writes(gobj, "the connection dropped during the write");
|
|
74649
|
+
}
|
|
74650
|
+
if (state === "ST_LOADING") {
|
|
74651
|
+
priv.load_round++;
|
|
74652
|
+
priv.pending = 0;
|
|
74653
|
+
priv.load_error = "";
|
|
74654
|
+
if (priv.model) return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
|
|
74655
|
+
(0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_IDLE");
|
|
74656
|
+
render(gobj);
|
|
74657
|
+
}
|
|
74456
74658
|
return 0;
|
|
74457
74659
|
}
|
|
74458
74660
|
/***************************************************************
|
|
@@ -74607,8 +74809,15 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
74607
74809
|
let kw_command = (0, _yuneta_gobj_js.kw_get_dict)(gobj, __command__, "kw", {}, 0);
|
|
74608
74810
|
let md = (0, _yuneta_gobj_js.is_object)(kw_command.__md_command__) ? kw_command.__md_command__ : kw_command;
|
|
74609
74811
|
if ((0, _yuneta_gobj_js.kw_get_str)(gobj, md, "purpose", "", 0) !== PURPOSE) return 0;
|
|
74812
|
+
if (result < 0 && !transport_in_session(gobj) && ((0, _yuneta_gobj_js.gobj_current_state)(gobj) === "ST_LOADING" || (0, _yuneta_gobj_js.gobj_current_state)(gobj) === "ST_SAVING")) {
|
|
74813
|
+
if (command === "nodes" && is_current_load(gobj, md) || command !== "nodes" && is_current_write(gobj, md)) return transport_dropped(gobj);
|
|
74814
|
+
}
|
|
74610
74815
|
if (command === "nodes") {
|
|
74611
74816
|
let topic_name = (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "topic_name", "", 0);
|
|
74817
|
+
if (!is_current_load(gobj, md)) {
|
|
74818
|
+
(0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: 'nodes' of '${topic_name}' answered for a load that is over: ignored`);
|
|
74819
|
+
return 0;
|
|
74820
|
+
}
|
|
74612
74821
|
priv.pending--;
|
|
74613
74822
|
if (result < 0) priv.load_error = comment || (0, i18next.t)("cannot load the schemas");
|
|
74614
74823
|
else if (priv.records && Array.isArray(priv.records[topic_name])) priv.records[topic_name] = Array.isArray(data) ? data : [];
|
|
@@ -74626,6 +74835,10 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
74626
74835
|
}
|
|
74627
74836
|
if (command === "update-node" || command === "delete-node") {
|
|
74628
74837
|
let topic_name = (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "topic_name", "", 0);
|
|
74838
|
+
if (!is_current_write(gobj, md)) {
|
|
74839
|
+
(0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' of '${topic_name}' answered for a write that is over (result ${result}): ignored`);
|
|
74840
|
+
return 0;
|
|
74841
|
+
}
|
|
74629
74842
|
if (result < 0) return end_writes(gobj, comment || (0, i18next.t)("the treedb refused the write"));
|
|
74630
74843
|
priv.save_wrote = true;
|
|
74631
74844
|
if (command === "delete-node") forget_record(gobj, topic_name, (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "record_id", "", 0));
|
|
@@ -74685,14 +74898,28 @@ function ac_hide(gobj, event, kw, src) {
|
|
|
74685
74898
|
return 0;
|
|
74686
74899
|
}
|
|
74687
74900
|
/***************************************************************
|
|
74688
|
-
* The session came up or went down. The model stays
|
|
74689
|
-
*
|
|
74690
|
-
*
|
|
74901
|
+
* The session came up or went down. The model stays -- a schema
|
|
74902
|
+
* does not change under you -- unless the drop cut a load or a
|
|
74903
|
+
* write (transport_dropped()): then it is asked again on the way
|
|
74904
|
+
* back, as it is when a first load had no transport to run on.
|
|
74905
|
+
*
|
|
74906
|
+
* Nothing else ends a load or a write the drop cut: their answers
|
|
74907
|
+
* will never come, and waiting in ST_LOADING skipped the reload
|
|
74908
|
+
* itself (M-1 of the independent review of 7.25.4).
|
|
74691
74909
|
***************************************************************/
|
|
74692
74910
|
function ac_transport_state(gobj, event, kw, src) {
|
|
74693
74911
|
let priv = gobj.priv;
|
|
74912
|
+
let state = (0, _yuneta_gobj_js.gobj_current_state)(gobj);
|
|
74694
74913
|
priv.connected = !!(kw && kw.connected);
|
|
74695
|
-
if (priv.connected
|
|
74914
|
+
if (!priv.connected) {
|
|
74915
|
+
if (state === "ST_LOADING" || state === "ST_SAVING") return transport_dropped(gobj);
|
|
74916
|
+
render_toolbar(gobj);
|
|
74917
|
+
return 0;
|
|
74918
|
+
}
|
|
74919
|
+
if ((!priv.model || priv.reload_on_open) && state !== "ST_LOADING" && state !== "ST_SAVING") {
|
|
74920
|
+
priv.reload_on_open = false;
|
|
74921
|
+
return request_model(gobj);
|
|
74922
|
+
}
|
|
74696
74923
|
render_toolbar(gobj);
|
|
74697
74924
|
return 0;
|
|
74698
74925
|
}
|
|
@@ -74710,18 +74937,22 @@ function ac_refresh$1(gobj, event, kw, src) {
|
|
|
74710
74937
|
}
|
|
74711
74938
|
/***************************************************************
|
|
74712
74939
|
* {drafts: {treedb_name: [topic names]}} -- the host says which
|
|
74713
|
-
* topics hold a draft in __system__ that
|
|
74714
|
-
*
|
|
74715
|
-
*
|
|
74716
|
-
*
|
|
74717
|
-
*
|
|
74718
|
-
*
|
|
74719
|
-
*
|
|
74940
|
+
* topics hold a draft in __system__ that is not saved (C_TREEDB's
|
|
74941
|
+
* saved-schema, `draft_changed`). The mark of a write lived in this
|
|
74942
|
+
* session's memory only, so a reload of the page, a reconnect or a
|
|
74943
|
+
* refresh showed no draft while __system__ still differed (N13 of
|
|
74944
|
+
* the 2026-09-22 review).
|
|
74945
|
+
*
|
|
74946
|
+
* REPLACES what the host said before -- the host is the truth after
|
|
74947
|
+
* a reload, and a Save is exactly the host saying "none any more".
|
|
74948
|
+
* It only ever added, and a topic stayed a draft after the Save that
|
|
74949
|
+
* published it (M1 of the 2026-09-23 review). What this session wrote
|
|
74950
|
+
* stays marked: it is kept apart (see topic_is_draft()).
|
|
74720
74951
|
***************************************************************/
|
|
74721
74952
|
function ac_drafts(gobj, event, kw, src) {
|
|
74722
74953
|
let priv = gobj.priv;
|
|
74723
74954
|
priv.host_drafts = kw && kw.drafts && typeof kw.drafts === "object" ? kw.drafts : {};
|
|
74724
|
-
|
|
74955
|
+
priv.host_ids = host_draft_ids(priv.model, priv.host_drafts);
|
|
74725
74956
|
if (priv.model) render(gobj);
|
|
74726
74957
|
return 0;
|
|
74727
74958
|
}
|