@yuneta/gobj-ui 7.23.31 → 7.23.33

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.
@@ -32,6 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  }) : target, mod));
33
33
  //#endregion
34
34
  let _yuneta_gobj_js = require("@yuneta/gobj-js");
35
+ _yuneta_gobj_js = __toESM(_yuneta_gobj_js, 1);
35
36
  let i18next = require("i18next");
36
37
  i18next = __toESM(i18next, 1);
37
38
  let tom_select = require("tom-select");
@@ -64092,6 +64093,86 @@ function register_c_yui_period() {
64092
64093
  return create_gclass(GCLASS_NAME);
64093
64094
  }
64094
64095
  //#endregion
64096
+ //#region src/dev_machine_trace.js
64097
+ /***********************************************************************
64098
+ * dev_machine_trace.js
64099
+ *
64100
+ * Reading a MACHINE trace line back.
64101
+ *
64102
+ * The developer window mirrors the runtime's log, and the
64103
+ * `machine` trace is most of what lands there once it is on.
64104
+ * Those lines arrive as text, so to the window they were just
64105
+ * "a debug log" -- which is why its `Periodic` filter could
64106
+ * not touch them: the filter works on a signature, and every
64107
+ * mirrored log carried the same one (`log:debug`).
64108
+ *
64109
+ * This gives a machine line back its EVENT, which is the only
64110
+ * thing that says whether it is noise. Both formats gobj-js
64111
+ * emits (`gobj_set_trace_machine_format`):
64112
+ *
64113
+ * 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
64114
+ * "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
64115
+ * 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
64116
+ *
64117
+ * Copyright (c) 2026, ArtGins.
64118
+ * All Rights Reserved.
64119
+ ***********************************************************************/
64120
+ var MACHINE_MARK_RE = /\bmach\(/;
64121
+ var EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
64122
+ var EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
64123
+ var PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
64124
+ /************************************************************
64125
+ * The event of a machine trace line, or "" when the line is
64126
+ * not one.
64127
+ *
64128
+ * "" is also the answer for a machine line that carries no
64129
+ * event -- a state change ("new st(…), prev st(…)") -- and
64130
+ * that is right: what it reports is not repetitive.
64131
+ ************************************************************/
64132
+ function machine_event_of(text) {
64133
+ if (typeof text !== "string" || text.length === 0) return "";
64134
+ if (MACHINE_MARK_RE.test(text)) {
64135
+ let m = EVENT_FIELD_RE.exec(text);
64136
+ return m ? m[1] : "";
64137
+ }
64138
+ let m = EVENT_HEAD_RE.exec(text);
64139
+ return m ? m[1] : "";
64140
+ }
64141
+ /************************************************************
64142
+ * Is this event one of the ones that arrive by themselves,
64143
+ * for ever, and say nothing about what the app is doing?
64144
+ ************************************************************/
64145
+ function is_periodic_event(event_name) {
64146
+ if (!event_name) return false;
64147
+ return PERIODIC_EVENT_RE.test(event_name);
64148
+ }
64149
+ /************************************************************
64150
+ * The signature of a mirrored log line: what the window
64151
+ * counts it under and what its filters match on.
64152
+ *
64153
+ * A machine line is signed by its EVENT, so a hundred
64154
+ * EV_TIMEOUTs are one recurring thing rather than a hundred
64155
+ * anonymous debug lines. Everything else is signed by its
64156
+ * level, as before.
64157
+ ************************************************************/
64158
+ function log_signature(level, text) {
64159
+ let event = machine_event_of(text);
64160
+ if (event) return "mach:" + event;
64161
+ return "log:" + (level || "debug");
64162
+ }
64163
+ /************************************************************
64164
+ * May the periodic filter hide this log line?
64165
+ *
64166
+ * Only a machine transition, and only below warning level.
64167
+ * An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
64168
+ * state", which is exactly the loud one the framework wants
64169
+ * seen -- must never disappear because of the word in it.
64170
+ ************************************************************/
64171
+ function log_is_periodic(level, text) {
64172
+ if (level === "error" || level === "warning") return false;
64173
+ return is_periodic_event(machine_event_of(text));
64174
+ }
64175
+ //#endregion
64095
64176
  //#region src/yui_dev.js
64096
64177
  /***********************************************************************
64097
64178
  * ui_dev.js
@@ -64228,8 +64309,9 @@ function dev_view() {
64228
64309
  let v = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_view_mode", "detailed", false);
64229
64310
  return v === "compact" || v === "name" || v === "full" ? v : "detailed";
64230
64311
  }
64312
+ var HIDE_PERIODIC_DEFAULT = 1;
64231
64313
  function dev_hide_periodic() {
64232
- return dev_num("dev_hide_periodic", 0) ? true : false;
64314
+ return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
64233
64315
  }
64234
64316
  function dev_route() {
64235
64317
  let v = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_output_route", "both", false);
@@ -64237,7 +64319,16 @@ function dev_route() {
64237
64319
  }
64238
64320
  function apply_console_route() {
64239
64321
  (0, _yuneta_gobj_js.set_console_log_enabled)(dev_route() !== "window");
64322
+ if (typeof _yuneta_gobj_js.set_console_log_filter === "function") _yuneta_gobj_js.set_console_log_filter((level, msg) => {
64323
+ if (!dev_hide_periodic()) return true;
64324
+ return !log_is_periodic(level, (0, _yuneta_gobj_js.is_string)(msg) ? msg : String(msg));
64325
+ });
64326
+ else if (!__console_filter_warned__) {
64327
+ __console_filter_warned__ = true;
64328
+ console.warn("yui_dev: gobj-js has no set_console_log_filter (needs >= 7.13.6): the Periodic filter applies to this window only, not to the console");
64329
+ }
64240
64330
  }
64331
+ var __console_filter_warned__ = false;
64241
64332
  function console_traffic(title, jn, direction, size) {
64242
64333
  let event = jn && jn.event ? String(jn.event) : "(no event)";
64243
64334
  let kw = jn && jn.kw && typeof jn.kw === "object" ? jn.kw : jn;
@@ -64267,6 +64358,7 @@ function set_output_route(v) {
64267
64358
  function toggle_pref(key, def) {
64268
64359
  let v = dev_num(key, def) ? 0 : 1;
64269
64360
  (0, _yuneta_gobj_js.kw_set_local_storage_value)(key, v);
64361
+ apply_console_route();
64270
64362
  rerender_all();
64271
64363
  refresh_dev_chrome();
64272
64364
  }
@@ -64305,7 +64397,10 @@ function build_filter_ctx() {
64305
64397
  };
64306
64398
  }
64307
64399
  function entry_hidden(e, ctx) {
64308
- if (e.kind === "log") return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64400
+ if (e.kind === "log") {
64401
+ if (ctx.hide_periodic && e.periodic) return true;
64402
+ return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64403
+ }
64309
64404
  if (e.dir === 1 && !ctx.out) return true;
64310
64405
  if (e.dir === 2 && !ctx.inc) return true;
64311
64406
  if (e.dir === 3 && !ctx.err) return true;
@@ -65006,7 +65101,8 @@ function info_log(level, msg, hora) {
65006
65101
  dir: 0,
65007
65102
  size: 0,
65008
65103
  ts: traffic_now(),
65009
- sig: "log:" + lvl,
65104
+ sig: log_signature(lvl, text),
65105
+ periodic: log_is_periodic(lvl, text),
65010
65106
  hay: (lvl + " " + text).toLowerCase(),
65011
65107
  $node: null
65012
65108
  };
@@ -65339,12 +65435,12 @@ function build_control_bar() {
65339
65435
  class: "YDEV_CHIP",
65340
65436
  "data-toggle": "periodic",
65341
65437
  type: "button",
65342
- title: "Hide recurring / periodic events (polls, heartbeats)"
65438
+ title: "Hide the timers: EV_TIMEOUT / PERIODIC / HEARTBEAT / PING transitions, and recurring traffic. On by default"
65343
65439
  },
65344
65440
  "⊘ Periodic",
65345
65441
  { click: (ev) => {
65346
65442
  ev.stopPropagation();
65347
- toggle_pref("dev_hide_periodic", 0);
65443
+ toggle_pref("dev_hide_periodic", HIDE_PERIODIC_DEFAULT);
65348
65444
  } }
65349
65445
  ];
65350
65446
  let search = [
@@ -1,3 +1,4 @@
1
+ import * as gobj_js from "@yuneta/gobj-js";
1
2
  import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
2
3
  import i18next, { t } from "i18next";
3
4
  import TomSelect from "tom-select";
@@ -64087,6 +64088,86 @@ function register_c_yui_period() {
64087
64088
  return create_gclass(GCLASS_NAME);
64088
64089
  }
64089
64090
  //#endregion
64091
+ //#region src/dev_machine_trace.js
64092
+ /***********************************************************************
64093
+ * dev_machine_trace.js
64094
+ *
64095
+ * Reading a MACHINE trace line back.
64096
+ *
64097
+ * The developer window mirrors the runtime's log, and the
64098
+ * `machine` trace is most of what lands there once it is on.
64099
+ * Those lines arrive as text, so to the window they were just
64100
+ * "a debug log" -- which is why its `Periodic` filter could
64101
+ * not touch them: the filter works on a signature, and every
64102
+ * mirrored log carried the same one (`log:debug`).
64103
+ *
64104
+ * This gives a machine line back its EVENT, which is the only
64105
+ * thing that says whether it is noise. Both formats gobj-js
64106
+ * emits (`gobj_set_trace_machine_format`):
64107
+ *
64108
+ * 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
64109
+ * "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
64110
+ * 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
64111
+ *
64112
+ * Copyright (c) 2026, ArtGins.
64113
+ * All Rights Reserved.
64114
+ ***********************************************************************/
64115
+ var MACHINE_MARK_RE = /\bmach\(/;
64116
+ var EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
64117
+ var EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
64118
+ var PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
64119
+ /************************************************************
64120
+ * The event of a machine trace line, or "" when the line is
64121
+ * not one.
64122
+ *
64123
+ * "" is also the answer for a machine line that carries no
64124
+ * event -- a state change ("new st(…), prev st(…)") -- and
64125
+ * that is right: what it reports is not repetitive.
64126
+ ************************************************************/
64127
+ function machine_event_of(text) {
64128
+ if (typeof text !== "string" || text.length === 0) return "";
64129
+ if (MACHINE_MARK_RE.test(text)) {
64130
+ let m = EVENT_FIELD_RE.exec(text);
64131
+ return m ? m[1] : "";
64132
+ }
64133
+ let m = EVENT_HEAD_RE.exec(text);
64134
+ return m ? m[1] : "";
64135
+ }
64136
+ /************************************************************
64137
+ * Is this event one of the ones that arrive by themselves,
64138
+ * for ever, and say nothing about what the app is doing?
64139
+ ************************************************************/
64140
+ function is_periodic_event(event_name) {
64141
+ if (!event_name) return false;
64142
+ return PERIODIC_EVENT_RE.test(event_name);
64143
+ }
64144
+ /************************************************************
64145
+ * The signature of a mirrored log line: what the window
64146
+ * counts it under and what its filters match on.
64147
+ *
64148
+ * A machine line is signed by its EVENT, so a hundred
64149
+ * EV_TIMEOUTs are one recurring thing rather than a hundred
64150
+ * anonymous debug lines. Everything else is signed by its
64151
+ * level, as before.
64152
+ ************************************************************/
64153
+ function log_signature(level, text) {
64154
+ let event = machine_event_of(text);
64155
+ if (event) return "mach:" + event;
64156
+ return "log:" + (level || "debug");
64157
+ }
64158
+ /************************************************************
64159
+ * May the periodic filter hide this log line?
64160
+ *
64161
+ * Only a machine transition, and only below warning level.
64162
+ * An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
64163
+ * state", which is exactly the loud one the framework wants
64164
+ * seen -- must never disappear because of the word in it.
64165
+ ************************************************************/
64166
+ function log_is_periodic(level, text) {
64167
+ if (level === "error" || level === "warning") return false;
64168
+ return is_periodic_event(machine_event_of(text));
64169
+ }
64170
+ //#endregion
64090
64171
  //#region src/yui_dev.js
64091
64172
  /***********************************************************************
64092
64173
  * ui_dev.js
@@ -64223,8 +64304,9 @@ function dev_view() {
64223
64304
  let v = kw_get_local_storage_value("dev_view_mode", "detailed", false);
64224
64305
  return v === "compact" || v === "name" || v === "full" ? v : "detailed";
64225
64306
  }
64307
+ var HIDE_PERIODIC_DEFAULT = 1;
64226
64308
  function dev_hide_periodic() {
64227
- return dev_num("dev_hide_periodic", 0) ? true : false;
64309
+ return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
64228
64310
  }
64229
64311
  function dev_route() {
64230
64312
  let v = kw_get_local_storage_value("dev_output_route", "both", false);
@@ -64232,7 +64314,16 @@ function dev_route() {
64232
64314
  }
64233
64315
  function apply_console_route() {
64234
64316
  set_console_log_enabled(dev_route() !== "window");
64317
+ if (typeof gobj_js.set_console_log_filter === "function") gobj_js.set_console_log_filter((level, msg) => {
64318
+ if (!dev_hide_periodic()) return true;
64319
+ return !log_is_periodic(level, is_string(msg) ? msg : String(msg));
64320
+ });
64321
+ else if (!__console_filter_warned__) {
64322
+ __console_filter_warned__ = true;
64323
+ console.warn("yui_dev: gobj-js has no set_console_log_filter (needs >= 7.13.6): the Periodic filter applies to this window only, not to the console");
64324
+ }
64235
64325
  }
64326
+ var __console_filter_warned__ = false;
64236
64327
  function console_traffic(title, jn, direction, size) {
64237
64328
  let event = jn && jn.event ? String(jn.event) : "(no event)";
64238
64329
  let kw = jn && jn.kw && typeof jn.kw === "object" ? jn.kw : jn;
@@ -64262,6 +64353,7 @@ function set_output_route(v) {
64262
64353
  function toggle_pref(key, def) {
64263
64354
  let v = dev_num(key, def) ? 0 : 1;
64264
64355
  kw_set_local_storage_value(key, v);
64356
+ apply_console_route();
64265
64357
  rerender_all();
64266
64358
  refresh_dev_chrome();
64267
64359
  }
@@ -64300,7 +64392,10 @@ function build_filter_ctx() {
64300
64392
  };
64301
64393
  }
64302
64394
  function entry_hidden(e, ctx) {
64303
- if (e.kind === "log") return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64395
+ if (e.kind === "log") {
64396
+ if (ctx.hide_periodic && e.periodic) return true;
64397
+ return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64398
+ }
64304
64399
  if (e.dir === 1 && !ctx.out) return true;
64305
64400
  if (e.dir === 2 && !ctx.inc) return true;
64306
64401
  if (e.dir === 3 && !ctx.err) return true;
@@ -65001,7 +65096,8 @@ function info_log(level, msg, hora) {
65001
65096
  dir: 0,
65002
65097
  size: 0,
65003
65098
  ts: traffic_now(),
65004
- sig: "log:" + lvl,
65099
+ sig: log_signature(lvl, text),
65100
+ periodic: log_is_periodic(lvl, text),
65005
65101
  hay: (lvl + " " + text).toLowerCase(),
65006
65102
  $node: null
65007
65103
  };
@@ -65334,12 +65430,12 @@ function build_control_bar() {
65334
65430
  class: "YDEV_CHIP",
65335
65431
  "data-toggle": "periodic",
65336
65432
  type: "button",
65337
- title: "Hide recurring / periodic events (polls, heartbeats)"
65433
+ title: "Hide the timers: EV_TIMEOUT / PERIODIC / HEARTBEAT / PING transitions, and recurring traffic. On by default"
65338
65434
  },
65339
65435
  "⊘ Periodic",
65340
65436
  { click: (ev) => {
65341
65437
  ev.stopPropagation();
65342
- toggle_pref("dev_hide_periodic", 0);
65438
+ toggle_pref("dev_hide_periodic", HIDE_PERIODIC_DEFAULT);
65343
65439
  } }
65344
65440
  ];
65345
65441
  let search = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.31",
3
+ "version": "7.23.33",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -0,0 +1,116 @@
1
+ /***********************************************************************
2
+ * dev_machine_trace.js
3
+ *
4
+ * Reading a MACHINE trace line back.
5
+ *
6
+ * The developer window mirrors the runtime's log, and the
7
+ * `machine` trace is most of what lands there once it is on.
8
+ * Those lines arrive as text, so to the window they were just
9
+ * "a debug log" -- which is why its `Periodic` filter could
10
+ * not touch them: the filter works on a signature, and every
11
+ * mirrored log carried the same one (`log:debug`).
12
+ *
13
+ * This gives a machine line back its EVENT, which is the only
14
+ * thing that says whether it is noise. Both formats gobj-js
15
+ * emits (`gobj_set_trace_machine_format`):
16
+ *
17
+ * 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
18
+ * "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
19
+ * 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
20
+ *
21
+ * Copyright (c) 2026, ArtGins.
22
+ * All Rights Reserved.
23
+ ***********************************************************************/
24
+
25
+
26
+ /* The verbose format names itself; the compact one does not. */
27
+ const MACHINE_MARK_RE = /\bmach\(/;
28
+
29
+ /* Verbose: the event is the `ev:` field. */
30
+ const EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
31
+
32
+ /* Compact: the event is the first token, behind the emoji. Anchored,
33
+ * so a line that merely MENTIONS an event ("subscribed to EV_X") is
34
+ * not mistaken for a transition. */
35
+ const EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
36
+
37
+ /* What "recurring" means by name. The count-based half of the filter
38
+ * lives in the window, which is the only thing that can count. */
39
+ const PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
40
+
41
+
42
+ /************************************************************
43
+ * The event of a machine trace line, or "" when the line is
44
+ * not one.
45
+ *
46
+ * "" is also the answer for a machine line that carries no
47
+ * event -- a state change ("new st(…), prev st(…)") -- and
48
+ * that is right: what it reports is not repetitive.
49
+ ************************************************************/
50
+ function machine_event_of(text)
51
+ {
52
+ if(typeof text !== "string" || text.length === 0) {
53
+ return "";
54
+ }
55
+
56
+ if(MACHINE_MARK_RE.test(text)) {
57
+ let m = EVENT_FIELD_RE.exec(text);
58
+ return m? m[1]: "";
59
+ }
60
+
61
+ let m = EVENT_HEAD_RE.exec(text);
62
+ return m? m[1]: "";
63
+ }
64
+
65
+ /************************************************************
66
+ * Is this event one of the ones that arrive by themselves,
67
+ * for ever, and say nothing about what the app is doing?
68
+ ************************************************************/
69
+ function is_periodic_event(event_name)
70
+ {
71
+ if(!event_name) {
72
+ return false;
73
+ }
74
+ return PERIODIC_EVENT_RE.test(event_name);
75
+ }
76
+
77
+ /************************************************************
78
+ * The signature of a mirrored log line: what the window
79
+ * counts it under and what its filters match on.
80
+ *
81
+ * A machine line is signed by its EVENT, so a hundred
82
+ * EV_TIMEOUTs are one recurring thing rather than a hundred
83
+ * anonymous debug lines. Everything else is signed by its
84
+ * level, as before.
85
+ ************************************************************/
86
+ function log_signature(level, text)
87
+ {
88
+ let event = machine_event_of(text);
89
+ if(event) {
90
+ return "mach:" + event;
91
+ }
92
+ return "log:" + (level || "debug");
93
+ }
94
+
95
+ /************************************************************
96
+ * May the periodic filter hide this log line?
97
+ *
98
+ * Only a machine transition, and only below warning level.
99
+ * An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
100
+ * state", which is exactly the loud one the framework wants
101
+ * seen -- must never disappear because of the word in it.
102
+ ************************************************************/
103
+ function log_is_periodic(level, text)
104
+ {
105
+ if(level === "error" || level === "warning") {
106
+ return false;
107
+ }
108
+ return is_periodic_event(machine_event_of(text));
109
+ }
110
+
111
+ export {
112
+ machine_event_of,
113
+ is_periodic_event,
114
+ log_signature,
115
+ log_is_periodic,
116
+ };
@@ -0,0 +1,98 @@
1
+ import {describe, it, expect} from "vitest";
2
+ import {
3
+ machine_event_of,
4
+ is_periodic_event,
5
+ log_signature,
6
+ log_is_periodic,
7
+ } from "./dev_machine_trace.js";
8
+
9
+ /* Real lines, copied from the developer window. */
10
+ const VERBOSE_IN =
11
+ "🔄 mach(C_TIMER^demo_yuno), st: ST_IDLE, ev: EV_TIMEOUT, ac: fi(), from(C_TIMER^demo_yuno)";
12
+ const VERBOSE_RET =
13
+ "<- mach(C_YUNO^demo_yuno), st: ST_IDLE, ev: EV_TIMEOUT_PERIODIC, ret: 0";
14
+ const VERBOSE_WORK =
15
+ "🔄 mach(C_YUI_SHELL^shell), st: ST_IDLE, ev: EV_ROUTE_CHANGED, ac: ac_route(), from(C_YUI_NAV^nav)";
16
+ const COMPACT =
17
+ "🔄 EV_TIMEOUT C_TIMER^demo_yuno ST_IDLE from C_TIMER^demo_yuno";
18
+ const STATE_CHANGE =
19
+ "🔀🔀 mach(C_TCP^conn), new st(ST_CONNECTED), prev st(ST_WAIT_CONNECTED)";
20
+ const NOT_DEFINED =
21
+ "📛 mach(C_X^x), st: ST_IDLE, ev: EV_TIMEOUT, 📛📛ERROR Event NOT DEFINED in state📛📛, from(C_TIMER^t)";
22
+
23
+ describe("machine_event_of", () => {
24
+ it("reads the event out of the verbose format, both lines of it", () => {
25
+ expect(machine_event_of(VERBOSE_IN)).toBe("EV_TIMEOUT");
26
+ expect(machine_event_of(VERBOSE_RET)).toBe("EV_TIMEOUT_PERIODIC");
27
+ expect(machine_event_of(VERBOSE_WORK)).toBe("EV_ROUTE_CHANGED");
28
+ });
29
+
30
+ it("reads it out of the compact format, where it leads the line", () => {
31
+ expect(machine_event_of(COMPACT)).toBe("EV_TIMEOUT");
32
+ });
33
+
34
+ it("a state change carries no event, and says so", () => {
35
+ expect(machine_event_of(STATE_CHANGE)).toBe("");
36
+ });
37
+
38
+ it("a line that merely MENTIONS an event is not a transition", () => {
39
+ expect(machine_event_of("subscribed to EV_TIMEOUT of C_TIMER^t")).toBe("");
40
+ expect(machine_event_of("cannot send EV_ON_OPEN")).toBe("");
41
+ });
42
+
43
+ it("is safe on anything that is not a line", () => {
44
+ expect(machine_event_of("")).toBe("");
45
+ expect(machine_event_of(null)).toBe("");
46
+ expect(machine_event_of(undefined)).toBe("");
47
+ expect(machine_event_of(42)).toBe("");
48
+ });
49
+ });
50
+
51
+ describe("is_periodic_event", () => {
52
+ it("names the events that arrive by themselves for ever", () => {
53
+ expect(is_periodic_event("EV_TIMEOUT")).toBe(true);
54
+ expect(is_periodic_event("EV_TIMEOUT_PERIODIC")).toBe(true);
55
+ expect(is_periodic_event("EV_HEARTBEAT")).toBe(true);
56
+ expect(is_periodic_event("EV_PING")).toBe(true);
57
+ });
58
+
59
+ it("leaves the ones a user caused alone", () => {
60
+ expect(is_periodic_event("EV_ROUTE_CHANGED")).toBe(false);
61
+ expect(is_periodic_event("EV_ON_MESSAGE")).toBe(false);
62
+ expect(is_periodic_event("")).toBe(false);
63
+ expect(is_periodic_event(null)).toBe(false);
64
+ });
65
+ });
66
+
67
+ describe("log_signature", () => {
68
+ it("signs a machine line by its EVENT, so a hundred of them are one thing", () => {
69
+ expect(log_signature("debug", VERBOSE_IN)).toBe("mach:EV_TIMEOUT");
70
+ expect(log_signature("debug", VERBOSE_RET)).toBe("mach:EV_TIMEOUT_PERIODIC");
71
+ expect(log_signature("debug", COMPACT)).toBe("mach:EV_TIMEOUT");
72
+ });
73
+
74
+ it("signs everything else by its level, as before", () => {
75
+ expect(log_signature("error", "connect failed")).toBe("log:error");
76
+ expect(log_signature("debug", "hello")).toBe("log:debug");
77
+ expect(log_signature("", "hello")).toBe("log:debug");
78
+ });
79
+ });
80
+
81
+ describe("log_is_periodic", () => {
82
+ it("hides the timer traffic the filter is for", () => {
83
+ expect(log_is_periodic("debug", VERBOSE_IN)).toBe(true);
84
+ expect(log_is_periodic("debug", VERBOSE_RET)).toBe(true);
85
+ expect(log_is_periodic("debug", COMPACT)).toBe(true);
86
+ });
87
+
88
+ it("NEVER hides an error or a warning that names a timeout", () => {
89
+ expect(log_is_periodic("error", NOT_DEFINED)).toBe(false);
90
+ expect(log_is_periodic("warning", VERBOSE_RET)).toBe(false);
91
+ expect(log_is_periodic("error", "connection timeout")).toBe(false);
92
+ });
93
+
94
+ it("leaves a transition somebody caused alone", () => {
95
+ expect(log_is_periodic("debug", VERBOSE_WORK)).toBe(false);
96
+ expect(log_is_periodic("debug", "just a log line")).toBe(false);
97
+ });
98
+ });
package/src/yui_dev.js CHANGED
@@ -21,6 +21,16 @@ import {
21
21
  gobj_set_trace_machine_format,
22
22
  } from "@yuneta/gobj-js";
23
23
 
24
+ /* A NAMESPACE import, deliberately, for one optional API.
25
+ *
26
+ * `set_console_log_filter` arrived in gobj-js 7.13.6, and a NAMED import of
27
+ * an export the installed copy does not have is a hard module-load error --
28
+ * it would take the whole app down instead of degrading. Read off the
29
+ * namespace, a missing export is plain `undefined` and can be tested for. */
30
+ import * as gobj_js from "@yuneta/gobj-js";
31
+
32
+ import {log_signature, log_is_periodic} from "./dev_machine_trace.js";
33
+
24
34
  import i18next from 'i18next';
25
35
 
26
36
  /***********************************************************************
@@ -164,9 +174,20 @@ function dev_view()
164
174
  return (v === "compact" || v === "name" || v === "full") ? v : "detailed";
165
175
  }
166
176
 
177
+ /* ON by default. The machine trace is the reason this window
178
+ * exists, and a yuno with one timer emits two lines a second for
179
+ * ever: leaving the filter off meant the first thing anybody saw
180
+ * after enabling Automata was a wall of EV_TIMEOUT with their own
181
+ * transitions somewhere inside it. The chip turns it back off.
182
+ *
183
+ * A CONSTANT, and not the literal in two places: the reader and the
184
+ * toggle must agree on what "unset" means, or the first click on the
185
+ * chip writes the value it already had and appears to do nothing. */
186
+ const HIDE_PERIODIC_DEFAULT = 1;
187
+
167
188
  function dev_hide_periodic()
168
189
  {
169
- return dev_num("dev_hide_periodic", 0) ? true : false;
190
+ return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
170
191
  }
171
192
 
172
193
  /* Where dev output (traffic + all logs + automata) is routed:
@@ -182,12 +203,46 @@ function dev_route()
182
203
  }
183
204
 
184
205
  /* Push the current route's console decision down to gobj-js. Called from
185
- * apply_dev_traces (startup) and whenever the Output selector changes. */
206
+ * apply_dev_traces (startup), whenever the Output selector changes, and
207
+ * whenever a filter that the console also obeys is toggled. */
186
208
  function apply_console_route()
187
209
  {
188
210
  set_console_log_enabled(dev_route() !== "window");
211
+
212
+ /* ONE rule, TWO sinks.
213
+ *
214
+ * The window's Periodic filter used to end at the window, so with the
215
+ * Output on "Both" the timers were gone from the panel and still
216
+ * arriving in the browser console, two lines a second — the same flood,
217
+ * one pane over. It cannot be fixed from this side alone: gobj-js writes
218
+ * the console line BEFORE it calls the log sink, so nothing here can
219
+ * un-print it. It hands out a say instead (`set_console_log_filter`,
220
+ * gobj-js >= 7.13.6), and this is the same predicate `entry_hidden()`
221
+ * uses, so the two panes cannot disagree about what is noise.
222
+ *
223
+ * Feature-detected rather than required: against an older gobj-js the
224
+ * window still filters and the console still floods, exactly as before,
225
+ * and the reason is logged once instead of failing. */
226
+ if(typeof gobj_js.set_console_log_filter === "function") {
227
+ gobj_js.set_console_log_filter((level, msg) => {
228
+ if(!dev_hide_periodic()) {
229
+ return true;
230
+ }
231
+ return !log_is_periodic(level, is_string(msg) ? msg : String(msg));
232
+ });
233
+ } else if(!__console_filter_warned__) {
234
+ __console_filter_warned__ = true;
235
+ console.warn(
236
+ "yui_dev: gobj-js has no set_console_log_filter (needs >= 7.13.6): " +
237
+ "the Periodic filter applies to this window only, not to the console"
238
+ );
239
+ }
189
240
  }
190
241
 
242
+ /* The warning above is worth saying, and worth saying ONCE: this runs on
243
+ * every Output change and every filter toggle. */
244
+ let __console_filter_warned__ = false;
245
+
191
246
  /* Emit one inter-event traffic line to the browser console (routes
192
247
  * "console" and "both"). gobj-js only knows about framework logs, not this
193
248
  * traffic feed, so the console mirror is produced here. */
@@ -240,6 +295,12 @@ function toggle_pref(key, def)
240
295
  {
241
296
  let v = dev_num(key, def) ? 0 : 1;
242
297
  kw_set_local_storage_value(key, v);
298
+ /* The console filter closes over dev_hide_periodic(), which reads the
299
+ * stored value on each call, so it follows this on its own. Re-pushed
300
+ * anyway: a filter installed before this module reached apply_dev_traces
301
+ * is the difference between the console obeying the chip and ignoring
302
+ * it, and one assignment is cheaper than that class of bug. */
303
+ apply_console_route();
243
304
  rerender_all();
244
305
  refresh_dev_chrome();
245
306
  }
@@ -300,7 +361,21 @@ function entry_hidden(e, ctx)
300
361
  {
301
362
  if(e.kind === "log") {
302
363
  /* Mirrored console logs respect the search box only — not the
303
- * in/out/err/periodic traffic filters. */
364
+ * in/out/err traffic filters, which are about DIRECTION and a
365
+ * log has none.
366
+ *
367
+ * The PERIODIC filter does reach them, and it has to: once
368
+ * Automata is on, the machine trace is what floods this
369
+ * window, and every one of those lines arrives here as a log.
370
+ * It applies to a machine TRANSITION only, and never above
371
+ * debug level — see log_is_periodic(). By NAME and not by
372
+ * count, unlike the traffic half below: a busy FSM crosses
373
+ * the recurrence threshold on almost every event within
374
+ * seconds, so counting here would empty the window of the
375
+ * very transitions somebody turned Automata on to read. */
376
+ if(ctx.hide_periodic && e.periodic) {
377
+ return true;
378
+ }
304
379
  return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
305
380
  }
306
381
  if(e.dir === 1 && !ctx.out) {
@@ -1031,10 +1106,16 @@ function info_log(level, msg, hora)
1031
1106
  } else {
1032
1107
  text = is_string(msg) ? msg : String(msg);
1033
1108
  }
1109
+ /* A machine line is signed by its EVENT, not by "log:debug":
1110
+ * that is what makes a hundred EV_TIMEOUTs ONE recurring
1111
+ * thing the filter and the counter can see, instead of a
1112
+ * hundred anonymous debug lines they cannot tell apart. */
1034
1113
  let entry = {
1035
1114
  kind: "log", level: lvl, text: text,
1036
1115
  dir: 0, size: 0, ts: traffic_now(),
1037
- sig: "log:" + lvl, hay: (lvl + " " + text).toLowerCase(), $node: null,
1116
+ sig: log_signature(lvl, text),
1117
+ periodic: log_is_periodic(lvl, text),
1118
+ hay: (lvl + " " + text).toLowerCase(), $node: null,
1038
1119
  };
1039
1120
  TRAFFIC_LOG.push(entry);
1040
1121
  if(TRAFFIC_LOG.length > TRAFFIC_MAX) {
@@ -1384,11 +1465,12 @@ function build_control_bar()
1384
1465
 
1385
1466
  let periodic_chip = ['button', {
1386
1467
  class: 'YDEV_CHIP', 'data-toggle': 'periodic', type: 'button',
1387
- title: 'Hide recurring / periodic events (polls, heartbeats)',
1468
+ title: 'Hide the timers: EV_TIMEOUT / PERIODIC / HEARTBEAT / PING ' +
1469
+ 'transitions, and recurring traffic. On by default',
1388
1470
  }, '⊘ Periodic', {
1389
1471
  click: (ev) => {
1390
1472
  ev.stopPropagation();
1391
- toggle_pref('dev_hide_periodic', 0);
1473
+ toggle_pref('dev_hide_periodic', HIDE_PERIODIC_DEFAULT);
1392
1474
  }
1393
1475
  }];
1394
1476