@yuneta/gobj-ui 7.23.30 → 7.23.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1430,7 +1430,19 @@ gclass of a **backend** yuno the host passes the answer of `view-gclass` as
1430
1430
 
1431
1431
  `gclass_view_available()` reports whether the app registers `C_YUI_JSON` — the
1432
1432
  gobj tree asks it before drawing its `gclass` button, so an app that does not
1433
- carry the viewer shows no control rather than one that fails.
1433
+ carry the viewer shows no control rather than one that fails. **Importing
1434
+ `c_yui_json.js` is not registering it**: an app that only pulls it in
1435
+ transitively (through the treedb graph, say) still has to call
1436
+ `register_c_yui_json()` for the button to appear.
1437
+
1438
+ > **Mount before you start a hosted view.** `mt_create` builds the DOM;
1439
+ > `mt_start` renders it, and a view that measures a canvas or observes its box
1440
+ > needs to be IN the document by then. A host that creates a viewer, reads its
1441
+ > `$container` and hands it to a window as `body` must therefore start the
1442
+ > viewer **after** the window — the window is what puts it in the document.
1443
+ > Started before, a graph viewer measures nothing, attaches no
1444
+ > `ResizeObserver`, and its canvas keeps its birth size for the life of the
1445
+ > window while everything resizes around it (fixed in 7.23.31).
1434
1446
 
1435
1447
  ### Toolbar badge — a count pinned to an item's icon
1436
1448
 
@@ -10983,6 +10983,26 @@ function make_toolbar$3(gobj) {
10983
10983
  return $toolbar;
10984
10984
  }
10985
10985
  /************************************************************
10986
+ * The element G6 mounts its canvas in.
10987
+ *
10988
+ * Read from THIS gclass's own $container, never with
10989
+ * `document.getElementById()`: a view is not always in the
10990
+ * document when it is started -- a host that builds the
10991
+ * viewer and mounts it into a window afterwards starts it
10992
+ * DETACHED -- and a global lookup answers null there. It cost
10993
+ * a resize that never worked: the ResizeObserver below was
10994
+ * simply never attached, silently, and the canvas kept the
10995
+ * size it was born with for the life of the window.
10996
+ *
10997
+ * Null only if the UI was never built; that is a bug, and it
10998
+ * is logged where it matters rather than skipped.
10999
+ ************************************************************/
11000
+ function canvas_mount$1(gobj) {
11001
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
11002
+ if (!$container) return null;
11003
+ return $container.querySelector("#" + gobj.priv.canvas_id);
11004
+ }
11005
+ /************************************************************
10986
11006
  * Build a G6 graph instance
10987
11007
  ************************************************************/
10988
11008
  function build_graph$3(gobj) {
@@ -11026,8 +11046,9 @@ function build_graph$3(gobj) {
11026
11046
  });
11027
11047
  graph.on(_antv_g6.CanvasEvent.CLICK, (evt) => {});
11028
11048
  if (typeof ResizeObserver !== "undefined") {
11029
- let $canvas = document.getElementById(priv.canvas_id);
11030
- if ($canvas) {
11049
+ let $canvas = canvas_mount$1(gobj);
11050
+ if (!$canvas) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no canvas mount, the graph will never resize`);
11051
+ else {
11031
11052
  let ro = new ResizeObserver(() => {
11032
11053
  if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
11033
11054
  priv._resize_raf = requestAnimationFrame(() => {
@@ -11964,10 +11985,9 @@ function ac_node_click$4(gobj, event, kw, src) {
11964
11985
  *
11965
11986
  ************************************************************/
11966
11987
  function ac_show$8(gobj, event, kw, src) {
11967
- let priv = gobj.priv;
11968
- let graph = priv.graph;
11988
+ let graph = gobj.priv.graph;
11969
11989
  if (graph) {
11970
- let $canvas = document.getElementById(priv.canvas_id);
11990
+ let $canvas = canvas_mount$1(gobj);
11971
11991
  if ($canvas) {
11972
11992
  let cw = $canvas.clientWidth;
11973
11993
  let ch = $canvas.clientHeight;
@@ -11986,10 +12006,9 @@ function ac_show$8(gobj, event, kw, src) {
11986
12006
  * No fitView: preserve the user's current pan/zoom.
11987
12007
  ************************************************************/
11988
12008
  function ac_resize$2(gobj, event, kw, src) {
11989
- let priv = gobj.priv;
11990
- let graph = priv.graph;
12009
+ let graph = gobj.priv.graph;
11991
12010
  if (graph) {
11992
- let $canvas = document.getElementById(priv.canvas_id);
12011
+ let $canvas = canvas_mount$1(gobj);
11993
12012
  if ($canvas) {
11994
12013
  let cw = $canvas.clientWidth;
11995
12014
  let ch = $canvas.clientHeight;
@@ -13843,7 +13862,6 @@ function open_gclass_view(host, gclass_name, opts) {
13843
13862
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(host)}: cannot create the gclass viewer`);
13844
13863
  return null;
13845
13864
  }
13846
- (0, _yuneta_gobj_js.gobj_start)(jv);
13847
13865
  let handle = {
13848
13866
  json_gobj: jv,
13849
13867
  win: null,
@@ -13867,6 +13885,7 @@ function open_gclass_view(host, gclass_name, opts) {
13867
13885
  t: i18next.t,
13868
13886
  on_close
13869
13887
  });
13888
+ (0, _yuneta_gobj_js.gobj_start)(jv);
13870
13889
  return handle;
13871
13890
  }
13872
13891
  let $win_parent = window_layer_of(host, shell);
@@ -13895,6 +13914,7 @@ function open_gclass_view(host, gclass_name, opts) {
13895
13914
  return null;
13896
13915
  }
13897
13916
  (0, _yuneta_gobj_js.gobj_start)(handle.win);
13917
+ (0, _yuneta_gobj_js.gobj_start)(jv);
13898
13918
  return handle;
13899
13919
  }
13900
13920
  /************************************************************
@@ -14636,6 +14656,26 @@ function make_toolbar$1(gobj) {
14636
14656
  return $toolbar;
14637
14657
  }
14638
14658
  /************************************************************
14659
+ * The element G6 mounts its canvas in.
14660
+ *
14661
+ * Read from THIS gclass's own $container, never with
14662
+ * `document.getElementById()`: a view is not always in the
14663
+ * document when it is started -- a host that builds the
14664
+ * viewer and mounts it into a window afterwards starts it
14665
+ * DETACHED -- and a global lookup answers null there. It cost
14666
+ * a resize that never worked: the ResizeObserver below was
14667
+ * simply never attached, silently, and the canvas kept the
14668
+ * size it was born with for the life of the window.
14669
+ *
14670
+ * Null only if the UI was never built; that is a bug, and it
14671
+ * is logged where it matters rather than skipped.
14672
+ ************************************************************/
14673
+ function canvas_mount(gobj) {
14674
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
14675
+ if (!$container) return null;
14676
+ return $container.querySelector("#" + gobj.priv.canvas_id);
14677
+ }
14678
+ /************************************************************
14639
14679
  * Build a G6 graph instance
14640
14680
  ************************************************************/
14641
14681
  function build_graph$2(gobj) {
@@ -14679,8 +14719,9 @@ function build_graph$2(gobj) {
14679
14719
  hide_popover(gobj);
14680
14720
  });
14681
14721
  if (typeof ResizeObserver !== "undefined") {
14682
- let $canvas = document.getElementById(priv.canvas_id);
14683
- if ($canvas) {
14722
+ let $canvas = canvas_mount(gobj);
14723
+ if (!$canvas) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no canvas mount, the graph will never resize`);
14724
+ else {
14684
14725
  let ro = new ResizeObserver(() => {
14685
14726
  if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
14686
14727
  priv._resize_raf = requestAnimationFrame(() => {
@@ -15678,12 +15719,11 @@ function ac_node_click$3(gobj, event, kw, src) {
15678
15719
  *
15679
15720
  ************************************************************/
15680
15721
  function ac_show$6(gobj, event, kw, src) {
15681
- let priv = gobj.priv;
15682
- let graph = priv.graph;
15722
+ let graph = gobj.priv.graph;
15683
15723
  save_view_state(gobj);
15684
15724
  refresh_tree(gobj);
15685
15725
  if (graph) {
15686
- let $canvas = document.getElementById(priv.canvas_id);
15726
+ let $canvas = canvas_mount(gobj);
15687
15727
  if ($canvas) {
15688
15728
  let cw = $canvas.clientWidth;
15689
15729
  let ch = $canvas.clientHeight;
@@ -15702,10 +15742,9 @@ function ac_show$6(gobj, event, kw, src) {
15702
15742
  * No fitView: preserve the user's current pan/zoom.
15703
15743
  ************************************************************/
15704
15744
  function ac_resize$1(gobj, event, kw, src) {
15705
- let priv = gobj.priv;
15706
- let graph = priv.graph;
15745
+ let graph = gobj.priv.graph;
15707
15746
  if (graph) {
15708
- let $canvas = document.getElementById(priv.canvas_id);
15747
+ let $canvas = canvas_mount(gobj);
15709
15748
  if ($canvas) {
15710
15749
  let cw = $canvas.clientWidth;
15711
15750
  let ch = $canvas.clientHeight;
@@ -55366,7 +55405,6 @@ function open_json_viewer(gobj) {
55366
55405
  return;
55367
55406
  }
55368
55407
  priv.json_gobj = jv;
55369
- (0, _yuneta_gobj_js.gobj_start)(jv);
55370
55408
  let $box = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(jv, "$container");
55371
55409
  if (mobile) {
55372
55410
  if (!shell) {
@@ -55385,6 +55423,7 @@ function open_json_viewer(gobj) {
55385
55423
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_JSON_CLOSED", {}, gobj);
55386
55424
  }
55387
55425
  });
55426
+ (0, _yuneta_gobj_js.gobj_start)(jv);
55388
55427
  } else {
55389
55428
  let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
55390
55429
  priv.json_win = (0, _yuneta_gobj_js.gobj_create_service)(`treedb-jsonwin-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj))}`, "C_YUI_WINDOW", {
@@ -55415,6 +55454,7 @@ function open_json_viewer(gobj) {
55415
55454
  return;
55416
55455
  }
55417
55456
  (0, _yuneta_gobj_js.gobj_start)(priv.json_win);
55457
+ (0, _yuneta_gobj_js.gobj_start)(jv);
55418
55458
  }
55419
55459
  request_print_tranger(gobj, "");
55420
55460
  }
@@ -55442,7 +55482,7 @@ function close_json_viewer(gobj) {
55442
55482
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: already gone: ${e}`);
55443
55483
  }
55444
55484
  if (jv && (0, _yuneta_gobj_js.is_gobj)(jv)) try {
55445
- (0, _yuneta_gobj_js.gobj_stop)(jv);
55485
+ if ((0, _yuneta_gobj_js.gobj_is_running)(jv)) (0, _yuneta_gobj_js.gobj_stop)(jv);
55446
55486
  (0, _yuneta_gobj_js.gobj_destroy)(jv);
55447
55487
  } catch (e) {
55448
55488
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: already gone: ${e}`);
@@ -64052,6 +64092,86 @@ function register_c_yui_period() {
64052
64092
  return create_gclass(GCLASS_NAME);
64053
64093
  }
64054
64094
  //#endregion
64095
+ //#region src/dev_machine_trace.js
64096
+ /***********************************************************************
64097
+ * dev_machine_trace.js
64098
+ *
64099
+ * Reading a MACHINE trace line back.
64100
+ *
64101
+ * The developer window mirrors the runtime's log, and the
64102
+ * `machine` trace is most of what lands there once it is on.
64103
+ * Those lines arrive as text, so to the window they were just
64104
+ * "a debug log" -- which is why its `Periodic` filter could
64105
+ * not touch them: the filter works on a signature, and every
64106
+ * mirrored log carried the same one (`log:debug`).
64107
+ *
64108
+ * This gives a machine line back its EVENT, which is the only
64109
+ * thing that says whether it is noise. Both formats gobj-js
64110
+ * emits (`gobj_set_trace_machine_format`):
64111
+ *
64112
+ * 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
64113
+ * "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
64114
+ * 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
64115
+ *
64116
+ * Copyright (c) 2026, ArtGins.
64117
+ * All Rights Reserved.
64118
+ ***********************************************************************/
64119
+ var MACHINE_MARK_RE = /\bmach\(/;
64120
+ var EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
64121
+ var EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
64122
+ var PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
64123
+ /************************************************************
64124
+ * The event of a machine trace line, or "" when the line is
64125
+ * not one.
64126
+ *
64127
+ * "" is also the answer for a machine line that carries no
64128
+ * event -- a state change ("new st(…), prev st(…)") -- and
64129
+ * that is right: what it reports is not repetitive.
64130
+ ************************************************************/
64131
+ function machine_event_of(text) {
64132
+ if (typeof text !== "string" || text.length === 0) return "";
64133
+ if (MACHINE_MARK_RE.test(text)) {
64134
+ let m = EVENT_FIELD_RE.exec(text);
64135
+ return m ? m[1] : "";
64136
+ }
64137
+ let m = EVENT_HEAD_RE.exec(text);
64138
+ return m ? m[1] : "";
64139
+ }
64140
+ /************************************************************
64141
+ * Is this event one of the ones that arrive by themselves,
64142
+ * for ever, and say nothing about what the app is doing?
64143
+ ************************************************************/
64144
+ function is_periodic_event(event_name) {
64145
+ if (!event_name) return false;
64146
+ return PERIODIC_EVENT_RE.test(event_name);
64147
+ }
64148
+ /************************************************************
64149
+ * The signature of a mirrored log line: what the window
64150
+ * counts it under and what its filters match on.
64151
+ *
64152
+ * A machine line is signed by its EVENT, so a hundred
64153
+ * EV_TIMEOUTs are one recurring thing rather than a hundred
64154
+ * anonymous debug lines. Everything else is signed by its
64155
+ * level, as before.
64156
+ ************************************************************/
64157
+ function log_signature(level, text) {
64158
+ let event = machine_event_of(text);
64159
+ if (event) return "mach:" + event;
64160
+ return "log:" + (level || "debug");
64161
+ }
64162
+ /************************************************************
64163
+ * May the periodic filter hide this log line?
64164
+ *
64165
+ * Only a machine transition, and only below warning level.
64166
+ * An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
64167
+ * state", which is exactly the loud one the framework wants
64168
+ * seen -- must never disappear because of the word in it.
64169
+ ************************************************************/
64170
+ function log_is_periodic(level, text) {
64171
+ if (level === "error" || level === "warning") return false;
64172
+ return is_periodic_event(machine_event_of(text));
64173
+ }
64174
+ //#endregion
64055
64175
  //#region src/yui_dev.js
64056
64176
  /***********************************************************************
64057
64177
  * ui_dev.js
@@ -64188,8 +64308,9 @@ function dev_view() {
64188
64308
  let v = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_view_mode", "detailed", false);
64189
64309
  return v === "compact" || v === "name" || v === "full" ? v : "detailed";
64190
64310
  }
64311
+ var HIDE_PERIODIC_DEFAULT = 1;
64191
64312
  function dev_hide_periodic() {
64192
- return dev_num("dev_hide_periodic", 0) ? true : false;
64313
+ return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
64193
64314
  }
64194
64315
  function dev_route() {
64195
64316
  let v = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_output_route", "both", false);
@@ -64265,7 +64386,10 @@ function build_filter_ctx() {
64265
64386
  };
64266
64387
  }
64267
64388
  function entry_hidden(e, ctx) {
64268
- if (e.kind === "log") return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64389
+ if (e.kind === "log") {
64390
+ if (ctx.hide_periodic && e.periodic) return true;
64391
+ return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64392
+ }
64269
64393
  if (e.dir === 1 && !ctx.out) return true;
64270
64394
  if (e.dir === 2 && !ctx.inc) return true;
64271
64395
  if (e.dir === 3 && !ctx.err) return true;
@@ -64966,7 +65090,8 @@ function info_log(level, msg, hora) {
64966
65090
  dir: 0,
64967
65091
  size: 0,
64968
65092
  ts: traffic_now(),
64969
- sig: "log:" + lvl,
65093
+ sig: log_signature(lvl, text),
65094
+ periodic: log_is_periodic(lvl, text),
64970
65095
  hay: (lvl + " " + text).toLowerCase(),
64971
65096
  $node: null
64972
65097
  };
@@ -65299,12 +65424,12 @@ function build_control_bar() {
65299
65424
  class: "YDEV_CHIP",
65300
65425
  "data-toggle": "periodic",
65301
65426
  type: "button",
65302
- title: "Hide recurring / periodic events (polls, heartbeats)"
65427
+ title: "Hide the timers: EV_TIMEOUT / PERIODIC / HEARTBEAT / PING transitions, and recurring traffic. On by default"
65303
65428
  },
65304
65429
  "⊘ Periodic",
65305
65430
  { click: (ev) => {
65306
65431
  ev.stopPropagation();
65307
- toggle_pref("dev_hide_periodic", 0);
65432
+ toggle_pref("dev_hide_periodic", HIDE_PERIODIC_DEFAULT);
65308
65433
  } }
65309
65434
  ];
65310
65435
  let search = [
@@ -10978,6 +10978,26 @@ function make_toolbar$3(gobj) {
10978
10978
  return $toolbar;
10979
10979
  }
10980
10980
  /************************************************************
10981
+ * The element G6 mounts its canvas in.
10982
+ *
10983
+ * Read from THIS gclass's own $container, never with
10984
+ * `document.getElementById()`: a view is not always in the
10985
+ * document when it is started -- a host that builds the
10986
+ * viewer and mounts it into a window afterwards starts it
10987
+ * DETACHED -- and a global lookup answers null there. It cost
10988
+ * a resize that never worked: the ResizeObserver below was
10989
+ * simply never attached, silently, and the canvas kept the
10990
+ * size it was born with for the life of the window.
10991
+ *
10992
+ * Null only if the UI was never built; that is a bug, and it
10993
+ * is logged where it matters rather than skipped.
10994
+ ************************************************************/
10995
+ function canvas_mount$1(gobj) {
10996
+ let $container = gobj_read_attr(gobj, "$container");
10997
+ if (!$container) return null;
10998
+ return $container.querySelector("#" + gobj.priv.canvas_id);
10999
+ }
11000
+ /************************************************************
10981
11001
  * Build a G6 graph instance
10982
11002
  ************************************************************/
10983
11003
  function build_graph$3(gobj) {
@@ -11021,8 +11041,9 @@ function build_graph$3(gobj) {
11021
11041
  });
11022
11042
  graph.on(CanvasEvent.CLICK, (evt) => {});
11023
11043
  if (typeof ResizeObserver !== "undefined") {
11024
- let $canvas = document.getElementById(priv.canvas_id);
11025
- if ($canvas) {
11044
+ let $canvas = canvas_mount$1(gobj);
11045
+ if (!$canvas) log_error(`${gobj_short_name(gobj)}: no canvas mount, the graph will never resize`);
11046
+ else {
11026
11047
  let ro = new ResizeObserver(() => {
11027
11048
  if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
11028
11049
  priv._resize_raf = requestAnimationFrame(() => {
@@ -11959,10 +11980,9 @@ function ac_node_click$4(gobj, event, kw, src) {
11959
11980
  *
11960
11981
  ************************************************************/
11961
11982
  function ac_show$8(gobj, event, kw, src) {
11962
- let priv = gobj.priv;
11963
- let graph = priv.graph;
11983
+ let graph = gobj.priv.graph;
11964
11984
  if (graph) {
11965
- let $canvas = document.getElementById(priv.canvas_id);
11985
+ let $canvas = canvas_mount$1(gobj);
11966
11986
  if ($canvas) {
11967
11987
  let cw = $canvas.clientWidth;
11968
11988
  let ch = $canvas.clientHeight;
@@ -11981,10 +12001,9 @@ function ac_show$8(gobj, event, kw, src) {
11981
12001
  * No fitView: preserve the user's current pan/zoom.
11982
12002
  ************************************************************/
11983
12003
  function ac_resize$2(gobj, event, kw, src) {
11984
- let priv = gobj.priv;
11985
- let graph = priv.graph;
12004
+ let graph = gobj.priv.graph;
11986
12005
  if (graph) {
11987
- let $canvas = document.getElementById(priv.canvas_id);
12006
+ let $canvas = canvas_mount$1(gobj);
11988
12007
  if ($canvas) {
11989
12008
  let cw = $canvas.clientWidth;
11990
12009
  let ch = $canvas.clientHeight;
@@ -13838,7 +13857,6 @@ function open_gclass_view(host, gclass_name, opts) {
13838
13857
  log_error(`${gobj_short_name(host)}: cannot create the gclass viewer`);
13839
13858
  return null;
13840
13859
  }
13841
- gobj_start(jv);
13842
13860
  let handle = {
13843
13861
  json_gobj: jv,
13844
13862
  win: null,
@@ -13862,6 +13880,7 @@ function open_gclass_view(host, gclass_name, opts) {
13862
13880
  t,
13863
13881
  on_close
13864
13882
  });
13883
+ gobj_start(jv);
13865
13884
  return handle;
13866
13885
  }
13867
13886
  let $win_parent = window_layer_of(host, shell);
@@ -13890,6 +13909,7 @@ function open_gclass_view(host, gclass_name, opts) {
13890
13909
  return null;
13891
13910
  }
13892
13911
  gobj_start(handle.win);
13912
+ gobj_start(jv);
13893
13913
  return handle;
13894
13914
  }
13895
13915
  /************************************************************
@@ -14631,6 +14651,26 @@ function make_toolbar$1(gobj) {
14631
14651
  return $toolbar;
14632
14652
  }
14633
14653
  /************************************************************
14654
+ * The element G6 mounts its canvas in.
14655
+ *
14656
+ * Read from THIS gclass's own $container, never with
14657
+ * `document.getElementById()`: a view is not always in the
14658
+ * document when it is started -- a host that builds the
14659
+ * viewer and mounts it into a window afterwards starts it
14660
+ * DETACHED -- and a global lookup answers null there. It cost
14661
+ * a resize that never worked: the ResizeObserver below was
14662
+ * simply never attached, silently, and the canvas kept the
14663
+ * size it was born with for the life of the window.
14664
+ *
14665
+ * Null only if the UI was never built; that is a bug, and it
14666
+ * is logged where it matters rather than skipped.
14667
+ ************************************************************/
14668
+ function canvas_mount(gobj) {
14669
+ let $container = gobj_read_attr(gobj, "$container");
14670
+ if (!$container) return null;
14671
+ return $container.querySelector("#" + gobj.priv.canvas_id);
14672
+ }
14673
+ /************************************************************
14634
14674
  * Build a G6 graph instance
14635
14675
  ************************************************************/
14636
14676
  function build_graph$2(gobj) {
@@ -14674,8 +14714,9 @@ function build_graph$2(gobj) {
14674
14714
  hide_popover(gobj);
14675
14715
  });
14676
14716
  if (typeof ResizeObserver !== "undefined") {
14677
- let $canvas = document.getElementById(priv.canvas_id);
14678
- if ($canvas) {
14717
+ let $canvas = canvas_mount(gobj);
14718
+ if (!$canvas) log_error(`${gobj_short_name(gobj)}: no canvas mount, the graph will never resize`);
14719
+ else {
14679
14720
  let ro = new ResizeObserver(() => {
14680
14721
  if (priv._resize_raf) cancelAnimationFrame(priv._resize_raf);
14681
14722
  priv._resize_raf = requestAnimationFrame(() => {
@@ -15673,12 +15714,11 @@ function ac_node_click$3(gobj, event, kw, src) {
15673
15714
  *
15674
15715
  ************************************************************/
15675
15716
  function ac_show$6(gobj, event, kw, src) {
15676
- let priv = gobj.priv;
15677
- let graph = priv.graph;
15717
+ let graph = gobj.priv.graph;
15678
15718
  save_view_state(gobj);
15679
15719
  refresh_tree(gobj);
15680
15720
  if (graph) {
15681
- let $canvas = document.getElementById(priv.canvas_id);
15721
+ let $canvas = canvas_mount(gobj);
15682
15722
  if ($canvas) {
15683
15723
  let cw = $canvas.clientWidth;
15684
15724
  let ch = $canvas.clientHeight;
@@ -15697,10 +15737,9 @@ function ac_show$6(gobj, event, kw, src) {
15697
15737
  * No fitView: preserve the user's current pan/zoom.
15698
15738
  ************************************************************/
15699
15739
  function ac_resize$1(gobj, event, kw, src) {
15700
- let priv = gobj.priv;
15701
- let graph = priv.graph;
15740
+ let graph = gobj.priv.graph;
15702
15741
  if (graph) {
15703
- let $canvas = document.getElementById(priv.canvas_id);
15742
+ let $canvas = canvas_mount(gobj);
15704
15743
  if ($canvas) {
15705
15744
  let cw = $canvas.clientWidth;
15706
15745
  let ch = $canvas.clientHeight;
@@ -55361,7 +55400,6 @@ function open_json_viewer(gobj) {
55361
55400
  return;
55362
55401
  }
55363
55402
  priv.json_gobj = jv;
55364
- gobj_start(jv);
55365
55403
  let $box = gobj_read_pointer_attr(jv, "$container");
55366
55404
  if (mobile) {
55367
55405
  if (!shell) {
@@ -55380,6 +55418,7 @@ function open_json_viewer(gobj) {
55380
55418
  gobj_send_event(gobj, "EV_JSON_CLOSED", {}, gobj);
55381
55419
  }
55382
55420
  });
55421
+ gobj_start(jv);
55383
55422
  } else {
55384
55423
  let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
55385
55424
  priv.json_win = gobj_create_service(`treedb-jsonwin-${clean_name(gobj_name(gobj))}`, "C_YUI_WINDOW", {
@@ -55410,6 +55449,7 @@ function open_json_viewer(gobj) {
55410
55449
  return;
55411
55450
  }
55412
55451
  gobj_start(priv.json_win);
55452
+ gobj_start(jv);
55413
55453
  }
55414
55454
  request_print_tranger(gobj, "");
55415
55455
  }
@@ -55437,7 +55477,7 @@ function close_json_viewer(gobj) {
55437
55477
  log_warning(`${gobj_short_name(gobj)}: already gone: ${e}`);
55438
55478
  }
55439
55479
  if (jv && is_gobj(jv)) try {
55440
- gobj_stop(jv);
55480
+ if (gobj_is_running(jv)) gobj_stop(jv);
55441
55481
  gobj_destroy(jv);
55442
55482
  } catch (e) {
55443
55483
  log_warning(`${gobj_short_name(gobj)}: already gone: ${e}`);
@@ -64047,6 +64087,86 @@ function register_c_yui_period() {
64047
64087
  return create_gclass(GCLASS_NAME);
64048
64088
  }
64049
64089
  //#endregion
64090
+ //#region src/dev_machine_trace.js
64091
+ /***********************************************************************
64092
+ * dev_machine_trace.js
64093
+ *
64094
+ * Reading a MACHINE trace line back.
64095
+ *
64096
+ * The developer window mirrors the runtime's log, and the
64097
+ * `machine` trace is most of what lands there once it is on.
64098
+ * Those lines arrive as text, so to the window they were just
64099
+ * "a debug log" -- which is why its `Periodic` filter could
64100
+ * not touch them: the filter works on a signature, and every
64101
+ * mirrored log carried the same one (`log:debug`).
64102
+ *
64103
+ * This gives a machine line back its EVENT, which is the only
64104
+ * thing that says whether it is noise. Both formats gobj-js
64105
+ * emits (`gobj_set_trace_machine_format`):
64106
+ *
64107
+ * 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
64108
+ * "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
64109
+ * 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
64110
+ *
64111
+ * Copyright (c) 2026, ArtGins.
64112
+ * All Rights Reserved.
64113
+ ***********************************************************************/
64114
+ var MACHINE_MARK_RE = /\bmach\(/;
64115
+ var EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
64116
+ var EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
64117
+ var PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
64118
+ /************************************************************
64119
+ * The event of a machine trace line, or "" when the line is
64120
+ * not one.
64121
+ *
64122
+ * "" is also the answer for a machine line that carries no
64123
+ * event -- a state change ("new st(…), prev st(…)") -- and
64124
+ * that is right: what it reports is not repetitive.
64125
+ ************************************************************/
64126
+ function machine_event_of(text) {
64127
+ if (typeof text !== "string" || text.length === 0) return "";
64128
+ if (MACHINE_MARK_RE.test(text)) {
64129
+ let m = EVENT_FIELD_RE.exec(text);
64130
+ return m ? m[1] : "";
64131
+ }
64132
+ let m = EVENT_HEAD_RE.exec(text);
64133
+ return m ? m[1] : "";
64134
+ }
64135
+ /************************************************************
64136
+ * Is this event one of the ones that arrive by themselves,
64137
+ * for ever, and say nothing about what the app is doing?
64138
+ ************************************************************/
64139
+ function is_periodic_event(event_name) {
64140
+ if (!event_name) return false;
64141
+ return PERIODIC_EVENT_RE.test(event_name);
64142
+ }
64143
+ /************************************************************
64144
+ * The signature of a mirrored log line: what the window
64145
+ * counts it under and what its filters match on.
64146
+ *
64147
+ * A machine line is signed by its EVENT, so a hundred
64148
+ * EV_TIMEOUTs are one recurring thing rather than a hundred
64149
+ * anonymous debug lines. Everything else is signed by its
64150
+ * level, as before.
64151
+ ************************************************************/
64152
+ function log_signature(level, text) {
64153
+ let event = machine_event_of(text);
64154
+ if (event) return "mach:" + event;
64155
+ return "log:" + (level || "debug");
64156
+ }
64157
+ /************************************************************
64158
+ * May the periodic filter hide this log line?
64159
+ *
64160
+ * Only a machine transition, and only below warning level.
64161
+ * An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
64162
+ * state", which is exactly the loud one the framework wants
64163
+ * seen -- must never disappear because of the word in it.
64164
+ ************************************************************/
64165
+ function log_is_periodic(level, text) {
64166
+ if (level === "error" || level === "warning") return false;
64167
+ return is_periodic_event(machine_event_of(text));
64168
+ }
64169
+ //#endregion
64050
64170
  //#region src/yui_dev.js
64051
64171
  /***********************************************************************
64052
64172
  * ui_dev.js
@@ -64183,8 +64303,9 @@ function dev_view() {
64183
64303
  let v = kw_get_local_storage_value("dev_view_mode", "detailed", false);
64184
64304
  return v === "compact" || v === "name" || v === "full" ? v : "detailed";
64185
64305
  }
64306
+ var HIDE_PERIODIC_DEFAULT = 1;
64186
64307
  function dev_hide_periodic() {
64187
- return dev_num("dev_hide_periodic", 0) ? true : false;
64308
+ return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
64188
64309
  }
64189
64310
  function dev_route() {
64190
64311
  let v = kw_get_local_storage_value("dev_output_route", "both", false);
@@ -64260,7 +64381,10 @@ function build_filter_ctx() {
64260
64381
  };
64261
64382
  }
64262
64383
  function entry_hidden(e, ctx) {
64263
- if (e.kind === "log") return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64384
+ if (e.kind === "log") {
64385
+ if (ctx.hide_periodic && e.periodic) return true;
64386
+ return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
64387
+ }
64264
64388
  if (e.dir === 1 && !ctx.out) return true;
64265
64389
  if (e.dir === 2 && !ctx.inc) return true;
64266
64390
  if (e.dir === 3 && !ctx.err) return true;
@@ -64961,7 +65085,8 @@ function info_log(level, msg, hora) {
64961
65085
  dir: 0,
64962
65086
  size: 0,
64963
65087
  ts: traffic_now(),
64964
- sig: "log:" + lvl,
65088
+ sig: log_signature(lvl, text),
65089
+ periodic: log_is_periodic(lvl, text),
64965
65090
  hay: (lvl + " " + text).toLowerCase(),
64966
65091
  $node: null
64967
65092
  };
@@ -65294,12 +65419,12 @@ function build_control_bar() {
65294
65419
  class: "YDEV_CHIP",
65295
65420
  "data-toggle": "periodic",
65296
65421
  type: "button",
65297
- title: "Hide recurring / periodic events (polls, heartbeats)"
65422
+ title: "Hide the timers: EV_TIMEOUT / PERIODIC / HEARTBEAT / PING transitions, and recurring traffic. On by default"
65298
65423
  },
65299
65424
  "⊘ Periodic",
65300
65425
  { click: (ev) => {
65301
65426
  ev.stopPropagation();
65302
- toggle_pref("dev_hide_periodic", 0);
65427
+ toggle_pref("dev_hide_periodic", HIDE_PERIODIC_DEFAULT);
65303
65428
  } }
65304
65429
  ];
65305
65430
  let search = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.30",
3
+ "version": "7.23.32",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -903,6 +903,30 @@ function make_toolbar(gobj)
903
903
  return $toolbar;
904
904
  }
905
905
 
906
+ /************************************************************
907
+ * The element G6 mounts its canvas in.
908
+ *
909
+ * Read from THIS gclass's own $container, never with
910
+ * `document.getElementById()`: a view is not always in the
911
+ * document when it is started -- a host that builds the
912
+ * viewer and mounts it into a window afterwards starts it
913
+ * DETACHED -- and a global lookup answers null there. It cost
914
+ * a resize that never worked: the ResizeObserver below was
915
+ * simply never attached, silently, and the canvas kept the
916
+ * size it was born with for the life of the window.
917
+ *
918
+ * Null only if the UI was never built; that is a bug, and it
919
+ * is logged where it matters rather than skipped.
920
+ ************************************************************/
921
+ function canvas_mount(gobj)
922
+ {
923
+ let $container = gobj_read_attr(gobj, "$container");
924
+ if(!$container) {
925
+ return null;
926
+ }
927
+ return $container.querySelector("#" + gobj.priv.canvas_id);
928
+ }
929
+
906
930
  /************************************************************
907
931
  * Build a G6 graph instance
908
932
  ************************************************************/
@@ -991,8 +1015,10 @@ function build_graph(gobj)
991
1015
  * the observed box so there is no feedback loop.
992
1016
  */
993
1017
  if(typeof ResizeObserver !== "undefined") {
994
- let $canvas = document.getElementById(priv.canvas_id);
995
- if($canvas) {
1018
+ let $canvas = canvas_mount(gobj);
1019
+ if(!$canvas) {
1020
+ log_error(`${gobj_short_name(gobj)}: no canvas mount, the graph will never resize`);
1021
+ } else {
996
1022
  let ro = new ResizeObserver(() => {
997
1023
  if(priv._resize_raf) {
998
1024
  cancelAnimationFrame(priv._resize_raf);
@@ -2501,7 +2527,7 @@ function ac_show(gobj, event, kw, src)
2501
2527
  refresh_tree(gobj);
2502
2528
 
2503
2529
  if(graph) {
2504
- let $canvas = document.getElementById(priv.canvas_id);
2530
+ let $canvas = canvas_mount(gobj);
2505
2531
  if($canvas) {
2506
2532
  /*
2507
2533
  * Content box (clientWidth/Height), not getBoundingClientRect:
@@ -2534,7 +2560,7 @@ function ac_resize(gobj, event, kw, src)
2534
2560
  let graph = priv.graph;
2535
2561
 
2536
2562
  if(graph) {
2537
- let $canvas = document.getElementById(priv.canvas_id);
2563
+ let $canvas = canvas_mount(gobj);
2538
2564
  if($canvas) {
2539
2565
  let cw = $canvas.clientWidth;
2540
2566
  let ch = $canvas.clientHeight;
@@ -674,6 +674,30 @@ function make_toolbar(gobj)
674
674
  return $toolbar;
675
675
  }
676
676
 
677
+ /************************************************************
678
+ * The element G6 mounts its canvas in.
679
+ *
680
+ * Read from THIS gclass's own $container, never with
681
+ * `document.getElementById()`: a view is not always in the
682
+ * document when it is started -- a host that builds the
683
+ * viewer and mounts it into a window afterwards starts it
684
+ * DETACHED -- and a global lookup answers null there. It cost
685
+ * a resize that never worked: the ResizeObserver below was
686
+ * simply never attached, silently, and the canvas kept the
687
+ * size it was born with for the life of the window.
688
+ *
689
+ * Null only if the UI was never built; that is a bug, and it
690
+ * is logged where it matters rather than skipped.
691
+ ************************************************************/
692
+ function canvas_mount(gobj)
693
+ {
694
+ let $container = gobj_read_attr(gobj, "$container");
695
+ if(!$container) {
696
+ return null;
697
+ }
698
+ return $container.querySelector("#" + gobj.priv.canvas_id);
699
+ }
700
+
677
701
  /************************************************************
678
702
  * Build a G6 graph instance
679
703
  ************************************************************/
@@ -778,8 +802,10 @@ function build_graph(gobj)
778
802
  * the observed box so there is no feedback loop.
779
803
  */
780
804
  if(typeof ResizeObserver !== "undefined") {
781
- let $canvas = document.getElementById(priv.canvas_id);
782
- if($canvas) {
805
+ let $canvas = canvas_mount(gobj);
806
+ if(!$canvas) {
807
+ log_error(`${gobj_short_name(gobj)}: no canvas mount, the graph will never resize`);
808
+ } else {
783
809
  let ro = new ResizeObserver(() => {
784
810
  if(priv._resize_raf) {
785
811
  cancelAnimationFrame(priv._resize_raf);
@@ -2333,7 +2359,7 @@ function ac_show(gobj, event, kw, src)
2333
2359
  let graph = priv.graph;
2334
2360
 
2335
2361
  if(graph) {
2336
- let $canvas = document.getElementById(priv.canvas_id);
2362
+ let $canvas = canvas_mount(gobj);
2337
2363
  if($canvas) {
2338
2364
  /*
2339
2365
  * Content box (clientWidth/Height), not getBoundingClientRect:
@@ -2366,7 +2392,7 @@ function ac_resize(gobj, event, kw, src)
2366
2392
  let graph = priv.graph;
2367
2393
 
2368
2394
  if(graph) {
2369
- let $canvas = document.getElementById(priv.canvas_id);
2395
+ let $canvas = canvas_mount(gobj);
2370
2396
  if($canvas) {
2371
2397
  let cw = $canvas.clientWidth;
2372
2398
  let ch = $canvas.clientHeight;
@@ -771,7 +771,14 @@ function open_json_viewer(gobj)
771
771
  return;
772
772
  }
773
773
  priv.json_gobj = jv;
774
- gobj_start(jv);
774
+
775
+ /* CREATED, not started. `mt_create` builds the DOM; `mt_start`
776
+ * RENDERS it -- and the viewer's graph view measures a canvas and
777
+ * puts a ResizeObserver on it, both of which need the element to be
778
+ * in the document, which the presenter below is what does. Started
779
+ * here it worked only while the reader had last used the tree view;
780
+ * with the graph view remembered it built detached, attached no
781
+ * observer, and the canvas never followed the window again. */
775
782
  let $box = gobj_read_pointer_attr(jv, "$container");
776
783
 
777
784
  if(mobile) {
@@ -793,6 +800,7 @@ function open_json_viewer(gobj)
793
800
  gobj_send_event(gobj, "EV_JSON_CLOSED", {}, gobj);
794
801
  }
795
802
  });
803
+ gobj_start(jv); /* mounted: now it can measure itself */
796
804
  } else {
797
805
  let $win_parent = (shell && yui_shell_popup_layer(shell)) ||
798
806
  (typeof document !== "undefined" && document.getElementById("top-layer")) ||
@@ -833,6 +841,7 @@ function open_json_viewer(gobj)
833
841
  return;
834
842
  }
835
843
  gobj_start(priv.json_win);
844
+ gobj_start(jv); /* mounted: now it can measure itself */
836
845
  }
837
846
 
838
847
  request_print_tranger(gobj, ""); /* first fetch: whole tranger, collapsed */
@@ -877,11 +886,16 @@ function close_json_viewer(gobj)
877
886
  }
878
887
  if(jv && is_gobj(jv)) {
879
888
  try {
880
- /* STOP, then destroy — the viewer was STARTED in open_json_viewer.
881
- * gobj_destroy() raises the `destroying` flag before it can stop a
882
- * running gobj, so destroying it straight logs "Destroying a
883
- * RUNNING gobj" + "gobj NULL or DESTROYED" and skips its mt_stop. */
884
- gobj_stop(jv);
889
+ /* STOP, then destroy: gobj_destroy() raises the `destroying`
890
+ * flag before it can stop a running gobj, so destroying it
891
+ * straight logs "Destroying a RUNNING gobj" + "gobj NULL or
892
+ * DESTROYED" and skips its mt_stop. GUARDED, because the
893
+ * viewer is now started AFTER its presenter is up: the two
894
+ * failure paths that get here arrive with it created and
895
+ * never started. */
896
+ if(gobj_is_running(jv)) {
897
+ gobj_stop(jv);
898
+ }
885
899
  gobj_destroy(jv);
886
900
  } catch(e) {
887
901
  log_warning(`${gobj_short_name(gobj)}: already gone: ${e}`);
@@ -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,8 @@ import {
21
21
  gobj_set_trace_machine_format,
22
22
  } from "@yuneta/gobj-js";
23
23
 
24
+ import {log_signature, log_is_periodic} from "./dev_machine_trace.js";
25
+
24
26
  import i18next from 'i18next';
25
27
 
26
28
  /***********************************************************************
@@ -164,9 +166,20 @@ function dev_view()
164
166
  return (v === "compact" || v === "name" || v === "full") ? v : "detailed";
165
167
  }
166
168
 
169
+ /* ON by default. The machine trace is the reason this window
170
+ * exists, and a yuno with one timer emits two lines a second for
171
+ * ever: leaving the filter off meant the first thing anybody saw
172
+ * after enabling Automata was a wall of EV_TIMEOUT with their own
173
+ * transitions somewhere inside it. The chip turns it back off.
174
+ *
175
+ * A CONSTANT, and not the literal in two places: the reader and the
176
+ * toggle must agree on what "unset" means, or the first click on the
177
+ * chip writes the value it already had and appears to do nothing. */
178
+ const HIDE_PERIODIC_DEFAULT = 1;
179
+
167
180
  function dev_hide_periodic()
168
181
  {
169
- return dev_num("dev_hide_periodic", 0) ? true : false;
182
+ return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
170
183
  }
171
184
 
172
185
  /* Where dev output (traffic + all logs + automata) is routed:
@@ -300,7 +313,21 @@ function entry_hidden(e, ctx)
300
313
  {
301
314
  if(e.kind === "log") {
302
315
  /* Mirrored console logs respect the search box only — not the
303
- * in/out/err/periodic traffic filters. */
316
+ * in/out/err traffic filters, which are about DIRECTION and a
317
+ * log has none.
318
+ *
319
+ * The PERIODIC filter does reach them, and it has to: once
320
+ * Automata is on, the machine trace is what floods this
321
+ * window, and every one of those lines arrives here as a log.
322
+ * It applies to a machine TRANSITION only, and never above
323
+ * debug level — see log_is_periodic(). By NAME and not by
324
+ * count, unlike the traffic half below: a busy FSM crosses
325
+ * the recurrence threshold on almost every event within
326
+ * seconds, so counting here would empty the window of the
327
+ * very transitions somebody turned Automata on to read. */
328
+ if(ctx.hide_periodic && e.periodic) {
329
+ return true;
330
+ }
304
331
  return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
305
332
  }
306
333
  if(e.dir === 1 && !ctx.out) {
@@ -1031,10 +1058,16 @@ function info_log(level, msg, hora)
1031
1058
  } else {
1032
1059
  text = is_string(msg) ? msg : String(msg);
1033
1060
  }
1061
+ /* A machine line is signed by its EVENT, not by "log:debug":
1062
+ * that is what makes a hundred EV_TIMEOUTs ONE recurring
1063
+ * thing the filter and the counter can see, instead of a
1064
+ * hundred anonymous debug lines they cannot tell apart. */
1034
1065
  let entry = {
1035
1066
  kind: "log", level: lvl, text: text,
1036
1067
  dir: 0, size: 0, ts: traffic_now(),
1037
- sig: "log:" + lvl, hay: (lvl + " " + text).toLowerCase(), $node: null,
1068
+ sig: log_signature(lvl, text),
1069
+ periodic: log_is_periodic(lvl, text),
1070
+ hay: (lvl + " " + text).toLowerCase(), $node: null,
1038
1071
  };
1039
1072
  TRAFFIC_LOG.push(entry);
1040
1073
  if(TRAFFIC_LOG.length > TRAFFIC_MAX) {
@@ -1384,11 +1417,12 @@ function build_control_bar()
1384
1417
 
1385
1418
  let periodic_chip = ['button', {
1386
1419
  class: 'YDEV_CHIP', 'data-toggle': 'periodic', type: 'button',
1387
- title: 'Hide recurring / periodic events (polls, heartbeats)',
1420
+ title: 'Hide the timers: EV_TIMEOUT / PERIODIC / HEARTBEAT / PING ' +
1421
+ 'transitions, and recurring traffic. On by default',
1388
1422
  }, '⊘ Periodic', {
1389
1423
  click: (ev) => {
1390
1424
  ev.stopPropagation();
1391
- toggle_pref('dev_hide_periodic', 0);
1425
+ toggle_pref('dev_hide_periodic', HIDE_PERIODIC_DEFAULT);
1392
1426
  }
1393
1427
  }];
1394
1428
 
@@ -115,8 +115,19 @@ function open_gclass_view(host, gclass_name, opts)
115
115
  log_error(`${gobj_short_name(host)}: cannot create the gclass viewer`);
116
116
  return null;
117
117
  }
118
- gobj_start(jv);
119
118
 
119
+ /* CREATED, not started. `mt_create` builds the DOM; `mt_start`
120
+ * RENDERS it -- and the viewer's graph view measures a canvas and
121
+ * puts a ResizeObserver on it, both of which need the element to be
122
+ * in the document. The window below is what puts it there, so the
123
+ * start goes after the window, at the bottom of this function.
124
+ *
125
+ * Starting it here worked for as long as the reader had last used
126
+ * the tree view: the graph is built on first entry, which was after
127
+ * the window. The moment the viewer REMEMBERED the graph view, it
128
+ * built it inside mt_start, detached, found no mount, attached no
129
+ * observer -- and the canvas kept its birth size for the life of
130
+ * the window while the window resized around it. */
120
131
  let handle = {json_gobj: jv, win: null, modal: null};
121
132
  let $box = gobj_read_pointer_attr(jv, "$container");
122
133
 
@@ -140,6 +151,7 @@ function open_gclass_view(host, gclass_name, opts)
140
151
  t: t,
141
152
  on_close: on_close,
142
153
  });
154
+ gobj_start(jv); /* mounted: now it can measure itself */
143
155
  return handle;
144
156
  }
145
157
 
@@ -180,6 +192,7 @@ function open_gclass_view(host, gclass_name, opts)
180
192
  return null;
181
193
  }
182
194
  gobj_start(handle.win);
195
+ gobj_start(jv); /* mounted: now it can measure itself */
183
196
 
184
197
  return handle;
185
198
  }