@yuneta/gobj-ui 7.25.13 → 7.25.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -15
- package/dist/gobj-ui.cjs.js +209 -74
- package/dist/gobj-ui.es.js +209 -74
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +42 -11
- package/src/c_yui_schema_editor.js +104 -37
- package/src/c_yui_treedb_graph.js +53 -6
- package/src/c_yui_treedb_topic_with_form.js +10 -10
- package/src/c_yui_treedb_topics.js +37 -32
- package/src/form_busy.js +2 -3
- package/src/form_time_value.js +1 -1
- package/src/form_time_value.test.js +1 -1
- package/src/form_writes_in_flight.js +1 -2
- package/src/g6_nodes_tree_fsm.test.js +33 -0
- package/src/graph_save_plan.js +28 -0
- package/src/graph_save_plan.test.js +36 -2
- package/src/host_drafts.js +2 -3
- package/src/schema_editor_drafts.wiring.test.js +1 -1
- package/src/schema_editor_reload.wiring.test.js +178 -46
- package/src/schema_editor_transport.wiring.test.js +2 -2
- package/src/schema_version_bump.test.js +1 -1
- package/src/shell_modals.js +2 -3
- package/src/shell_toast_once.test.js +10 -10
- package/src/table_cell_text.js +1 -1
- package/src/treedb_graph_writes.wiring.test.js +208 -0
- package/src/treedb_topics_transport.wiring.test.js +80 -12
- package/src/yui_row_search.test.js +1 -1
- package/src/yui_table_select.test.js +1 -1
|
@@ -160,10 +160,11 @@ function mt_create(gobj)
|
|
|
160
160
|
* and serial (form_writes_in_flight): answered refused when the
|
|
161
161
|
* transport closes under them. */
|
|
162
162
|
gobj.priv.form_writes = {};
|
|
163
|
-
/* The
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
|
|
163
|
+
/* The session dropped since the tables were read: every open table
|
|
164
|
+
* may be stale (a write the drop cut, node events published while
|
|
165
|
+
* nobody heard them). Read again when the session is back
|
|
166
|
+
* (reread_open_topics), never out of session. */
|
|
167
|
+
gobj.priv.reread_on_session = false;
|
|
167
168
|
gobj.priv.conn_shell = null; /* the shell whose EV_CONNECTION_STATE we hear */
|
|
168
169
|
|
|
169
170
|
build_ui(gobj);
|
|
@@ -187,7 +188,7 @@ function mt_start(gobj)
|
|
|
187
188
|
* (yui_shell_set_connection_state). A write in flight when it drops
|
|
188
189
|
* is never answered, and its form waited for ever in every host
|
|
189
190
|
* that did not forward EV_TRANSPORT_STATE -- wattyzer and yunovatios
|
|
190
|
-
* among them
|
|
191
|
+
* among them. The transport itself is
|
|
191
192
|
* not subscribed to: see ac_transport_state(). */
|
|
192
193
|
let shell = yui_shell_of(gobj);
|
|
193
194
|
if(shell) {
|
|
@@ -2111,7 +2112,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2111
2112
|
log_warning(`${gobj_short_name(gobj)}: the write of ` +
|
|
2112
2113
|
`'${failed_topic}' was cut by the drop: the topic is ` +
|
|
2113
2114
|
`read again when the session is back`);
|
|
2114
|
-
gobj.priv.
|
|
2115
|
+
gobj.priv.reread_on_session = true;
|
|
2115
2116
|
}
|
|
2116
2117
|
}
|
|
2117
2118
|
/* Answered once: the transport closing may have answered
|
|
@@ -2475,18 +2476,17 @@ function ac_transport_state(gobj, event, kw, src)
|
|
|
2475
2476
|
|
|
2476
2477
|
/*
|
|
2477
2478
|
* A write in flight when the transport closes is never answered by
|
|
2478
|
-
* the backend: its form stayed open and busy for ever
|
|
2479
|
-
*
|
|
2479
|
+
* the backend: its form stayed open and busy for ever. Answered
|
|
2480
|
+
* refused: the form stays open on what
|
|
2480
2481
|
* was typed, and the person saves again when the session is back.
|
|
2481
2482
|
*/
|
|
2482
2483
|
if(!connected) {
|
|
2484
|
+
/* What happens in the store while the session is down reaches
|
|
2485
|
+
* no table: its node events are published to nobody. Every open
|
|
2486
|
+
* table is read again when the session is back. */
|
|
2487
|
+
gobj.priv.reread_on_session = true;
|
|
2483
2488
|
abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
|
|
2484
2489
|
log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
|
|
2485
|
-
/* Whether it reached the store is unknown: read it again
|
|
2486
|
-
* when the session is back. */
|
|
2487
|
-
if(w.topic_name) {
|
|
2488
|
-
gobj.priv.reload_on_session[w.topic_name] = true;
|
|
2489
|
-
}
|
|
2490
2490
|
answer_form_write(
|
|
2491
2491
|
gobj,
|
|
2492
2492
|
get_gobj_formtable(gobj, w.topic_name),
|
|
@@ -2495,32 +2495,37 @@ function ac_transport_state(gobj, event, kw, src)
|
|
|
2495
2495
|
);
|
|
2496
2496
|
});
|
|
2497
2497
|
} else {
|
|
2498
|
-
|
|
2498
|
+
reread_open_topics(gobj);
|
|
2499
2499
|
}
|
|
2500
2500
|
return 0;
|
|
2501
2501
|
}
|
|
2502
2502
|
|
|
2503
2503
|
/************************************************************
|
|
2504
|
-
* The session is back: read
|
|
2505
|
-
*
|
|
2506
|
-
*
|
|
2507
|
-
*
|
|
2508
|
-
*
|
|
2509
|
-
*
|
|
2504
|
+
* The session is back after a drop: read every open table again,
|
|
2505
|
+
* as the schema editor reloads its model. A drop cuts the writes
|
|
2506
|
+
* in flight AND the node events published meanwhile, so any
|
|
2507
|
+
* table may be stale, not only the one a cut write left.
|
|
2508
|
+
*
|
|
2509
|
+
* Only when the TRANSPORT says so too -- the app's connection
|
|
2510
|
+
* (EV_CONNECTION_STATE) can report "up" before the transport of
|
|
2511
|
+
* this view is in session, and a read asked then is refused. The
|
|
2512
|
+
* read waits for an edge that finds the transport in session, and
|
|
2513
|
+
* is done once, though both edges report the same "up".
|
|
2510
2514
|
************************************************************/
|
|
2511
|
-
function
|
|
2515
|
+
function reread_open_topics(gobj)
|
|
2512
2516
|
{
|
|
2513
2517
|
let priv = gobj.priv;
|
|
2514
|
-
if(!is_connected(gobj)) {
|
|
2518
|
+
if(!priv.reread_on_session || !is_connected(gobj)) {
|
|
2515
2519
|
return;
|
|
2516
2520
|
}
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2521
|
+
priv.reread_on_session = false;
|
|
2522
|
+
gobj_match_children(gobj, {
|
|
2523
|
+
__gclass_name__: "C_YUI_TREEDB_TOPIC_WITH_FORM"
|
|
2524
|
+
}).forEach((form) => {
|
|
2525
|
+
let topic_name = gobj_read_attr(form, "topic_name");
|
|
2526
|
+
if(topic_name) {
|
|
2527
|
+
gobj_send_event(gobj, "EV_REFRESH_TOPIC", {topic_name: topic_name}, gobj);
|
|
2522
2528
|
}
|
|
2523
|
-
gobj_send_event(gobj, "EV_REFRESH_TOPIC", {topic_name: topic_name}, gobj);
|
|
2524
2529
|
});
|
|
2525
2530
|
}
|
|
2526
2531
|
|
|
@@ -2643,8 +2648,8 @@ function ac_create_record(gobj, event, kw, src)
|
|
|
2643
2648
|
|
|
2644
2649
|
/* `create_only`: +New makes a NEW record. With `create` alone a taken
|
|
2645
2650
|
* id was an update -- the record overwritten and, through autolink
|
|
2646
|
-
* with the selects empty, unlinked, answered "Node update!"
|
|
2647
|
-
*
|
|
2651
|
+
* with the selects empty, unlinked, answered "Node update!". A
|
|
2652
|
+
* backend older than the option ignores it
|
|
2648
2653
|
* and keeps the old upsert. */
|
|
2649
2654
|
let options = {
|
|
2650
2655
|
list_dict: true,
|
|
@@ -2890,8 +2895,8 @@ function ac_update_record(gobj, event, kw, src)
|
|
|
2890
2895
|
/************************************************************
|
|
2891
2896
|
* A write sent with no session never leaves: C_IEVENT_CLI logs
|
|
2892
2897
|
* "Not in session" and answers null, as it does when the command
|
|
2893
|
-
* went -- so a form waiting for the answer waited for ever
|
|
2894
|
-
*
|
|
2898
|
+
* went -- so a form waiting for the answer waited for ever. Refused
|
|
2899
|
+
* before it is sent, aloud.
|
|
2895
2900
|
************************************************************/
|
|
2896
2901
|
function refuse_without_session(gobj, topic_name)
|
|
2897
2902
|
{
|
package/src/form_busy.js
CHANGED
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
* that back. So a second busy(true) while already busy must not
|
|
11
11
|
* run at all -- it read the buttons it had just disabled as
|
|
12
12
|
* "disabled on their own", and the busy(false) of a refused write
|
|
13
|
-
* left Save and Cancel dead (
|
|
14
|
-
*
|
|
15
|
-
* the write). The caller owns `state` ({busy}) for the form's life.
|
|
13
|
+
* left Save and Cancel dead (a record with a picked file went
|
|
14
|
+
* busy for the read, then busy again for the write). The caller owns `state` ({busy}) for the form's life.
|
|
16
15
|
*
|
|
17
16
|
* Copyright (c) 2026, ArtGins.
|
|
18
17
|
* All Rights Reserved.
|
package/src/form_time_value.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* A Date -> the value of a `datetime-local` input, in local time,
|
|
38
38
|
* WITH seconds: the input is built with `step: 1`, which is what
|
|
39
39
|
* lets it hold them. Written as "YYYY-MM-DDTHH:mm" it lost them on
|
|
40
|
-
* every save of any field
|
|
40
|
+
* every save of any field.
|
|
41
41
|
************************************************************/
|
|
42
42
|
function date_to_datetime_local(value)
|
|
43
43
|
{
|
|
@@ -4,7 +4,7 @@ import {date_to_datetime_local, datetime_local_to_epoch} from "./form_time_value
|
|
|
4
4
|
describe("form_time_value", () => {
|
|
5
5
|
|
|
6
6
|
it("an epoch with seconds survives a round trip through the input", () => {
|
|
7
|
-
/*
|
|
7
|
+
/* The input was written without
|
|
8
8
|
* seconds, so every save of ANY field moved a writable time
|
|
9
9
|
* column back up to 59 s. */
|
|
10
10
|
const epoch = 1790000037; // :37 seconds
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* answers, so the host has to answer EVERY way a write ends --
|
|
8
8
|
* and the transport closing under a write in flight, or a
|
|
9
9
|
* command that never left because there was no session, are two
|
|
10
|
-
* of them that nobody answered: the form stayed busy for ever
|
|
11
|
-
* (N8 of the 2026-09-22 review).
|
|
10
|
+
* of them that nobody answered: the form stayed busy for ever.
|
|
12
11
|
*
|
|
13
12
|
* A write is known by its TOPIC and its `form_write` serial,
|
|
14
13
|
* never by the serial alone: each topic has its own form, and
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* g6_nodes_tree_fsm.test.js
|
|
3
|
+
*
|
|
4
|
+
* The machine of the real C_G6_NODES_TREE: the events its host
|
|
5
|
+
* sends it are declared, and each has an action. The host's own
|
|
6
|
+
* tests drive a fake engine, which cannot see this.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2026, ArtGins.
|
|
9
|
+
* All Rights Reserved.
|
|
10
|
+
***********************************************************************/
|
|
11
|
+
import {describe, test, expect} from "vitest";
|
|
12
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
13
|
+
|
|
14
|
+
install_dom_double();
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
gclass_find_by_name, gclass_event_type, gclass_check_fsm,
|
|
18
|
+
} = await import("@yuneta/gobj-js");
|
|
19
|
+
const {register_c_g6_nodes_tree} = await import("./c_g6_nodes_tree.js");
|
|
20
|
+
|
|
21
|
+
describe("C_G6_NODES_TREE", () => {
|
|
22
|
+
|
|
23
|
+
test("hears EV_GRAPHS_WRITE_REFUSED, the host's word on a __graphs__ write", () => {
|
|
24
|
+
register_c_g6_nodes_tree();
|
|
25
|
+
const gclass = gclass_find_by_name("C_G6_NODES_TREE", false);
|
|
26
|
+
expect(gclass).toBeTruthy();
|
|
27
|
+
expect(gclass_event_type(gclass, "EV_GRAPHS_WRITE_REFUSED")).toBeTruthy();
|
|
28
|
+
const idle = gclass.dl_states.find((st) => st.state_name === "ST_IDLE");
|
|
29
|
+
expect(idle.dl_actions.some((a) => a.event_name === "EV_GRAPHS_WRITE_REFUSED" &&
|
|
30
|
+
typeof a.action === "function")).toBe(true);
|
|
31
|
+
expect(gclass_check_fsm(gclass)).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
});
|
package/src/graph_save_plan.js
CHANGED
|
@@ -140,7 +140,35 @@ function apply_graphs_echo(live, saved, records, rec)
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/***************************************************************
|
|
144
|
+
* A write of one topic's arrangement did NOT land: the backend
|
|
145
|
+
* refused it, the transport refused it on the way out, or there
|
|
146
|
+
* was no session to send it on.
|
|
147
|
+
*
|
|
148
|
+
* saved {topic_name: properties} what the backend has
|
|
149
|
+
* topic_name the topic of the refused record
|
|
150
|
+
*
|
|
151
|
+
* The view records a write as saved BEFORE it leaves, because
|
|
152
|
+
* nothing answers it when it lands. So a refused one left the view
|
|
153
|
+
* believing the backend held an arrangement it does not, and the
|
|
154
|
+
* next Save found nothing to write. What the backend holds for the
|
|
155
|
+
* topic is not known any more, so it is forgotten: the next Save
|
|
156
|
+
* writes the topic again (plan_graph_saves()).
|
|
157
|
+
*
|
|
158
|
+
* Returns TRUE if there was something to forget.
|
|
159
|
+
***************************************************************/
|
|
160
|
+
function forget_refused_graphs_write(saved, topic_name)
|
|
161
|
+
{
|
|
162
|
+
if(!is_object(saved) || !topic_name ||
|
|
163
|
+
!Object.prototype.hasOwnProperty.call(saved, topic_name)) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
delete saved[topic_name];
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
143
170
|
export {
|
|
171
|
+
forget_refused_graphs_write,
|
|
144
172
|
apply_graphs_echo,
|
|
145
173
|
plan_graph_saves,
|
|
146
174
|
topic_arrangement_changed,
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
***********************************************************************/
|
|
19
19
|
import {describe, test, expect} from "vitest";
|
|
20
20
|
import {
|
|
21
|
-
plan_graph_saves, topic_arrangement_changed, arrangement_of, apply_graphs_echo
|
|
21
|
+
plan_graph_saves, topic_arrangement_changed, arrangement_of, apply_graphs_echo,
|
|
22
|
+
forget_refused_graphs_write
|
|
22
23
|
} from "./graph_save_plan.js";
|
|
23
24
|
|
|
24
25
|
const ORIGIN = "f3163a9f-1f37-412a-b8c9-87a4341f14de";
|
|
@@ -150,7 +151,7 @@ describe("arrangement_of", () => {
|
|
|
150
151
|
* save by another browser, sends back. It used to rebuild the saved
|
|
151
152
|
* snapshot of EVERY topic, taking the copies from the live objects the
|
|
152
153
|
* view had already rearranged: what was unsaved in OTHER topics counted
|
|
153
|
-
* as saved, and the next Save skipped it
|
|
154
|
+
* as saved, and the next Save skipped it.
|
|
154
155
|
*/
|
|
155
156
|
describe("an echo of one __graphs__ record", () => {
|
|
156
157
|
function state()
|
|
@@ -200,3 +201,36 @@ describe("an echo of one __graphs__ record", () => {
|
|
|
200
201
|
expect(live.yunos).toBeDefined();
|
|
201
202
|
});
|
|
202
203
|
});
|
|
204
|
+
|
|
205
|
+
/*
|
|
206
|
+
* A write of `__graphs__` that the backend REFUSED. The view takes the
|
|
207
|
+
* write for granted before it leaves (nothing answers it when it lands),
|
|
208
|
+
* so a refused one was recorded as saved: the next Save found nothing to
|
|
209
|
+
* write, and the arrangement was never stored.
|
|
210
|
+
*/
|
|
211
|
+
describe("a refused __graphs__ write", () => {
|
|
212
|
+
|
|
213
|
+
test("is planned again by the next Save", () => {
|
|
214
|
+
let live = five_topics();
|
|
215
|
+
let saved = five_topics();
|
|
216
|
+
live.yunos.nodes["1"].x = 500;
|
|
217
|
+
expect(plan_graph_saves(live, saved)).toEqual(["yunos"]);
|
|
218
|
+
|
|
219
|
+
/* What save_topic_graph_properties() does before the publish. */
|
|
220
|
+
saved.yunos = JSON.parse(JSON.stringify(live.yunos));
|
|
221
|
+
expect(plan_graph_saves(live, saved)).toEqual([]);
|
|
222
|
+
|
|
223
|
+
expect(forget_refused_graphs_write(saved, "yunos")).toBe(true);
|
|
224
|
+
expect(plan_graph_saves(live, saved)).toEqual(["yunos"]);
|
|
225
|
+
expect(Object.keys(saved)).not.toContain("yunos");
|
|
226
|
+
expect(Object.keys(saved).length).toBe(4);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("a topic the view never believed saved: nothing to forget", () => {
|
|
230
|
+
let saved = five_topics();
|
|
231
|
+
expect(forget_refused_graphs_write(saved, "nothing")).toBe(false);
|
|
232
|
+
expect(forget_refused_graphs_write(saved, "")).toBe(false);
|
|
233
|
+
expect(forget_refused_graphs_write(null, "yunos")).toBe(false);
|
|
234
|
+
expect(Object.keys(saved).length).toBe(5);
|
|
235
|
+
});
|
|
236
|
+
});
|
package/src/host_drafts.js
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* it, and that mark lived in the memory of the session only:
|
|
8
8
|
* a reload of the page, a reconnect or a refresh of the model
|
|
9
9
|
* lost the chip, the banner and the export warning while
|
|
10
|
-
* __system__ still differed from the file in use
|
|
11
|
-
*
|
|
12
|
-
* C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
10
|
+
* __system__ still differed from the file in use. The host
|
|
11
|
+
* reads that difference from C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
13
12
|
* hands it here; the editor applies it to its model, in place
|
|
14
13
|
* of what the host said before.
|
|
15
14
|
*
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* EV_DRAFTS driven through a real C_YUI_SCHEMA_EDITOR on a document
|
|
5
5
|
* double. The helper was right and tested; the ACTION folded the
|
|
6
6
|
* host's marks into the session's, so a topic the host named before
|
|
7
|
-
* a Save stayed a draft after it
|
|
7
|
+
* a Save stayed a draft after it.
|
|
8
8
|
*
|
|
9
9
|
* Copyright (c) 2026, ArtGins.
|
|
10
10
|
* All Rights Reserved.
|